级别: 初级
Greg Travis (mito@panix.com), 自由程序员
2001 年 8 月 04 日
JDBC 提供了一种强大、全面的接口用来从 Java 程序访问数据库。对于较小的项目来说,使用 JDBC 似乎是理所当然的,它使一些程序员避免了一起使用数据库。本文描述了一种简单的包装器库,它让使用简单的数据库易如反掌。您会发现您已经开始想在编写的每一个程序中都使用 JDBC。
事情发生得很突然。您正在修改一个程序,您原以为它是个小程序,不料竟发现它已经迅速增长成为一个庞大的东西 ― 一个 项目― 而现在您已到了需要保存一些数据的时候了。
问题是,您并不是有意让程序发展到这种程度的。 您只是不由自主地做了一小部分修改。您并没有真正准备要存储或装入。而且,您的程序是个 applet,而 applet 是无法存储或装入的。
可以存储或装入它们吗?
实际上,JDBC API 允许任何 Java 程序 ― 甚至是 applet ― 连接到关系型数据库(RDBMS)上。 不幸的是,JDBC 对您的小程序来说可能有一点头重脚轻了。毕竟,如果您希望做的只是存储一点点数据的话,RDBMS 是一个强大、复杂的系统。
在本文中,我们将分析一个简单的使用 JDBC 的抽象层。对于简单的应用程序来说,它可以让您只用几行代码就实现存储和装入结构化数据。它不会处理复杂的 RDBMS 使用,但那正是我们要避免的,不是吗?
JDBC 的复杂性
JDBC 使用起来可能是一个复杂的 API。它不仅必须支持整个强大的 SQL 标准,还必须很好地隐藏不同数据库引擎之间的区别。
JDBC 的复杂还在于关系型数据库是基于 SQL 构建的,而 SQL 是要给人用的,而不是给程序用的。直接使用 JDBC 有点象同时用两种语言编程。
虽然 JDBC 的这些方面并不是什么坏事,但它们确实与我们的目标 ― 快速地存储少量数据相冲突。
简化的抽象
我们将创建一个简单的抽象层,让您不必顾虑所有繁琐的细节问题来直接使用 JDBC。如果您早已熟悉 JDBC 或关系型数据库了,那您一眼看到我们的类列表应该是很熟悉的:
Database
Table
RowSet
Row
我们这里不是在作任何实质性的事情;我们的数据模型本质上和关系型模型是一样的,但去掉了烦人的细节(同时去掉了强大的功能)。每一个类映射到一个基本的 RDBMS 概念上,同时也映射到一个 JDBC 类上。就是这种映射让我们的 API 可以在保持易用性的同时保留它的相关特性。
这种 API 的设计是基于对我们的数据存储需要的设想。如果您发现自己的程序需要一点不同的地方,您可以随意地改变这种抽象以适应您的情况。 这些类可以被认为是一种简化您工作的模式,而不是一成不变的规则。
如果您不熟悉 SQL 或者 RDBMS 技术,不必害怕。 下面的四节中,每一节都会帮助您熟悉我们的一个类,还有这些类映射到的 RDBMS 功能。
Database 类
当使用 JDBC 与数据库建立连接时,您必须告诉 JDBC 在何处可以找到实际的数据。 因为不同的数据库引擎有不同的访问方法和描述这些方法的不同语法,所以有不止一种方法来指定数据源。 在 JDBC 中,统一资源标识符(Uniform Resource Identifier,URI)字符串是用来指定数据源的,而这个字符串的结构是依赖于数据库的。
Database 类的主要目的就是封装这个字符串,还有建立连接操作时可能需要的任何用户名/密码信息。
下面是如何创建一个 Database 对象的方法:
Database db =
new Database( "jdbc:postgresql://localhost/mito",
"mito", "" );
|
构造函数的第一个参数是数据源的 URI。 在这个示例中,我使用了 PostgreSQL数据库引擎,而且在本机上访问了一个名为 mito 的数据库。 另外,我指定我的用户名 mito 和一个空的密码分别作为第二个和第三个参数。
一旦您创建了 Database 对象,您就可以使用它来访问数据,如我们在下一章可以看到的一样。
Table 类
我们在 API 中对简化的一个设想就是,当您从表的一行读取数据时,您会得到整行的数据。换句话说,表的一行是作为读写单独一块数据的最小单位。这并不十分有效,但效率不是我们方法中所首要考虑的。
Talbe 类让您可以读写这些行对象。您必须做的第一步是创建一个表对象,它简单得只要知道它的名称即可:
Table table = db.getTable( "employee" );
|
创建 Table 对象的操作实际上并没有做任何事,只是让对象记住自己的名称。要做一些实际的事,我们就需要实际地使用这个 Table 对象了。在这里,我们从表中读取一行。
Row row = table.getRow( "id=101");
|
注意,我们已经指定了我们只需要那些‘id’值设定为‘101’的行。通过使用 getRow() 方法,我们假定只有一行符合这个条件。在另外的情况下,我们可能需要多个行,那样我们就需要这样使用 getRows() 方法:
RowSet rows = table.getRows( "id<103" );
|
在这种情况下,返回的值是一个 RowSet ,而不是一个 Row。 RowSet 就是 Row 的一个集合。
在接下来的两节里,我们将讨论 Row 和 RowSet 类。
Row 类
在我们的抽象中, Row 是在 RDBMS 中表示表中一行的名称/值对的集合。不同于 RDBMS 值可以是不同的类型, Row 仅包含一种类型,即字符串类型。 这还是为了让事情简单一点 ― 我们假定您不需要字符串类型提供的任何更强大的功能。
一旦您有了一个 Row ,就很容易从其中取出一个值,正如我们在清单 1 中看见的一样。
L清单 1. 从 Row 中获取值
Row e = table.getRow( "id=100" );
String name = e.get( "name" );
System.out.println( "Employee name: "+name );
|
还要注意的是 Row 是排序的,这样您就可以通过索引来取出名称/值的对。清单 2 中给出了这样的一个示例。
清单 2. 迭代整个 Row
Row e = table.getRow( "id=100" );
for (int i=0; i<e.length(); ++i) {
String key = e.getKey( i );
String value = e.get( i );
System.out.println( key+" = "+value );
}
|
当然,您还可以改变 Row 中的值。这是在数据库中更改数据所必需的 — 稍后我们会看到这一点。
RowSet 类
记住有一些查询可以返回多个 Row ,这样的话您就会得到一个 RowSet 。 RowSet 有一点不同于基于 Vector 的包装器。你可以轻易地迭代 RowSet 中所有的 Row ,在清单 3 中可以看到这一点。
清单 3. 迭代整个 RowSet
RowSet rs = table.get( "id<104" );
for (int i=0; i<rs.length(); ++i) {
Row row = rs.get( i );
// do something with the row....
}
|
一个完整的示例
现在我们已经看过了所有的类,让我们来看一个完整的示例吧。在清单 4 中,我们将抽取出符合特定条件的一套记录,然后打印出它们的值。
清单 4. 一个读取数据的完整示例
// First, get all rows meeting the criterion
RowSet rs = table.getRows( "id<103" );
// Iterate through the set
for (int i=0; i<rs.length(); ++i) {
// Grab each row in turn
Row row = rs.get( i );
// Get and print the value of the "name" field
String name = row.get( "name" );
System.out.println( "Name: "+name );
}
|
如此容易!在下一节中,我们将看看怎样向数据库写入数据。
修改数据
正如前面所提到的,使用我们的 API 读写数据是以整个 行为单位的。为了向数据库写入数据,您必须创建(或修改) Row 对象,然后向数据库写入那个 Row 对象。
向数据库写入数据是通过使用 Table 中的 putRow 方法。这种方法有两种变体:
public void putRow( Row row )
public void putRow( Row row, String conditions )
这两种变体分别对应于 SQL 中的 INSERT 和 UPDATE 命令。
在第一个变体中,写一行意味着将一个全新的行插入表中。
在第二个变体中,写一行意味着修改一个现有的行。 conditions 参数使您能够指定您想要修改的是哪一行(哪些行)。
让我们来看看每种方法的一个示例。
插入一个新行
插入一个新行很简单,因为您不必指定要修改的行(一行或多行)。您只是简单地把行插入:
您可以重新创建一个 Row ,如清单 5 所示。
清单 5. 重新创建一个 Row
// Create an empty row object
Row row = new Row();
// Fill it up with data
row.put( "id", "200" );
row.put( "name", "Joey Capellino" );
|
或者,您可以修改一个以前曾经从数据库中读取的一个现有的行,如清单 6 所示。
清单 6. 修改现有的 Row
// Grab a row from the database
Row row = table.getRow( someConditions );
// Change some or all of the fields
row.put( "name", "Joey Capellino" );
|
虽然通常是在插入时重新创建 Row ,更新时使用现有的 Row ,实际上您可以用任何方式来进行。
更新现有的行
正如前面的部分提到的,对于您如何 创建用来更新的 Row 是没有限制的。 但是,通常您是使用一个刚从数据库中读出的 Row 。
为了详细描述这一点,我们将使用一个示例(在该例子中我们读出一个员工的姓名),改变这个名字,然后将更改后的结果写回数据库,如清单 7 所示。
清单 7. 通过修改 Row 进行更新
Row row = table.getRow( "id=104" );
row.put( "name", newName );
table.putRow( row, "id=104" );
|
注意我们必须在调用 putRow() 中指定条件。这样才会使调用成为 更新,而不是 插入。
注意,这个调用将更新 所有符合条件的行,而不是其中的一行。
结论
在本文中,我们初步认识了一种通过 JDBC 包提供一种简化的通往关系型数据库接口的 API。这种抽象保留了 JDBC 接口的很多基本关系型功能,但对其进行了简化,从而让使用非常地方便。这种简化是以效率为代价的,但当目标是简单性时,这并不是一个令人惊奇的结果。
参考资料
关于作者
|
|
|
Greg Travis 是居住在纽约的一个自由程序员。他对计算机的兴趣可能是源于“ Bionic Woman”中的一段情节 - Jamie 四处奔跑,试图逃离一所灯光和门都被邪恶的人工智能所控制的房子,人工智能还通过扬声器嘲笑她。他是一个传统观念的虔诚信徒 - 如果一个计算机程序能够工作,那完全是个巧合。可以通过 mito@panix.com与他联系。
|
|