|  | 级别: 中级 Robert Brunner (rb@ncsa.uiuc.edu), NCSA 研究科学家、天文学助理教授, University of Illinois, Urbana-Champaign
2007 年 6 月 07 日 了解在将预处理语句与 Apache Derby 数据库应用程序结合使用时可用的高级功能。首先,使用 ij 工具动态执行 PreparedStatement 快速构建原型。接下来,把数据注入 PreparedStatement 以便高效地将大量数据插入到特定列中。然后使用 ParameterMetaData 对象获取信息,包括关于 PreparedStatement 中各个参数特定于供应商的实现细节。
PreparedStatement 的高级功能
在本系列的上一篇文章 “用 Apache Derby 进行开发 —— 取得节节胜利,第 3 部分: 修改数据”(developerWorks,2007 年 2 月)中,您了解了 JDBC PreparedStatement 对象。通过使用 PreparedStatement,可以在运行时动态修改 SQL 语句。使用这项技术,可以通过重用相同的 SQL 语句插入大量信息,也可以通过更改参数值有选择性地查询、删除或更新数据库中的行。
后续的 JDBC 修订版已经修改了 PreparedStatement 接口,使该接口可以提供附加功能,本文将对附加功能进行详细探究。但是,首先,您将了解如何从 ij 工具使用 PreparedStatement。虽然您可能没认识到,但是 ij 程序是一个功能强大的工具,使您可以在不编译或调试 Java 代码的情况下针对 Apache Derby 数据库动态尝试不同的 SQL 语句。这种动态本性将允许您以类似于使用 Python 等脚本语言的方式,快速连接至数据库、尝试并修改一连串的 SQL 命令,直至执行所需的操作。您可能更喜欢使用 Java 语言开发工业级应用程序,但是快速构建原型和评估的能力是一项十分重要的技巧,它通常可以帮助缩短开发时间。
幸运的是,通过 ij 工具来使用 PreparedStatement 十分简单,如清单 1 所示。首先,需要创建工作目录并解压缩附带的源代码(请参阅 下载 部分)。接下来,将编译本文中稍后提供的 Java 源代码,然后可以执行构建脚本以正确初始化 Apache Derby 演示数据库。
清单 1. 通过 ij 工具来使用预处理语句
rb$ mkdir derbyWork
rb$ cd derbyWork/
rb$ unzip ../derby13.zip
Archive: ../derby13.zip
inflating: PreparedMetaData.java
inflating: PreparedStream.java
inflating: bio.txt
inflating: derby.build.sql
inflating: p-rbrunner.jpg
rb$ javac *.java
rb$ java org.apache.derby.tools.ij < derby.build.sql
...
rb$ java org.apache.derby.tools.ij
ij version 10.2
ij> CONNECT 'jdbc:derby:test' ;
ij> AUTOCOMMIT OFF ;
ij> PREPARE pstmt AS
'INSERT INTO bigdog.products(itemNumber, price, stockDate, description)
VALUES(?, ?, ?, ?)' ;
ij> EXECUTE pstmt USING
'VALUES (11, 88.99, ''2007-02-28'', ''Board rack'')' ;
1 row inserted/updated/deleted
ij> EXECUTE pstmt USING
'VALUES (12, 199.99, ''2007-01-14'', ''Board bag, black'')' ;
1 row inserted/updated/deleted
ij> COMMIT ;
ij> REMOVE pstmt ;
ij> EXECUTE pstmt USING
'VALUES (13, 1.99, ''2007-01-21'', ''Board keychain'')' ;
IJ ERROR: Unable to establish prepared statement PSTMT
ij> PREPARE pstmt AS
'SELECT itemNumber, price, stockDate, description
FROM bigdog.products WHERE itemNumber > ?' ;
ij> EXECUTE pstmt USING 'VALUES (10)' ;
ITEMNUMBER |PRICE |STOCKDATE |DESCRIPTION
-----------------------------------------------------------------------
11 |88.99 |2007-02-28|Board rack
12 |199.99 |2007-01-14|Board bag, black
2 rows selected
ij> REMOVE pstmt ;
ij> EXIT ; |
初始化演示数据库后,可以使用 Apache Derby ij 工具连接至测试数据库以及发出 SQL 命令。首先,应当关闭自动提交模式,这样,每条 SQL 语句的结果不会被自动提交给数据库。当使用 ij 工具动态评估 SQL 命令时这样做通常十分有用,因为它将简化撤消过程。在这种情况下,需要禁用自动提交模式,因为将多次重用同一条 JDBC PreparedStatement。
要在 ij 工具内创建一条新的 PreparedStatement,可以如 清单 1 所示使用 PREPARE 命令。这样做将定义一条名为 pstmt 的新 PreparedStatement,它可用于把新数据插入 bigdog.products 表中。在创建了这条新 PreparedStatement 之后,可以通过使用 EXECUTE 命令来使用它把新数据插入 products 表。在本例中,首先将插入两行新数据,然后使用 COMMIT 命令把两个新行保存到数据库中。
使用 PreparedStatement 对象完成操作后,需要通知该数据库分配给 PreparedStatement 的内部数据库资源可以释放了。通过 ij 工具,可以使用 REMOVE 命令删除 PreparedStatement 来完成此过程。如果尝试在删除后重用这条 PreparedStatement,将生成错误,因为内部资源不再可用。
如本例中所示,还可以创建一条 PreparedStatement 来使用 ij 工具执行动态查询。为此,可以相应地编写 SELECT 查询来定义一条新的 PreparedStatement。要执行新查询,需要传入适当的值;在这种情况下,您将看到先前插入的两个新行。
使用 PreparedStatement 注入数据
 |
JDBC 4.0 和 Apache Derby JDBC 4.0 规范已经发布,并且它包括若干个与 PreparedStatement 的使用直接相关的改进。选择可与 Apache Derby 结合使用的 JDBC 4.0 功能,但是只能在使用 Java Platform, Standard Edition (Java SE) 6 虚拟机时使用这些功能。此外,目前可以下载的 Apache Derby JDBC 驱动程序必须使用 Java SE 6 版本编译器重新编译,才能利用规范附带的任何新功能。默认情况下,目前下载的 Apache Derby JDBC 驱动程序被编译为支持 Java Development Kit (JDK) 1.4 或 1.5 虚拟机上的 JDBC 3.0 规范。 |
|
先前,您使用了 PreparedStatement 从 Java 程序和 ij 工具把数据插入 Apache Derby 数据库。但在某些情况下,您可能需要把大量数据插入一列 —— 而不仅是一个整型值或简短的描述性字符串。例如,可能需要插入产品的图片或较长的产品描述。为了简化将大量数据插入数据库列的操作,JDBC 规范支持三种可用于直接把数据注入数据库列的流:
- 二进制流,用于未解释的数据字节
- ASCII 流,用于 ASCII 字符数据
- Unicode 流,用于 Unicode 字符数据
要使用流,需要像以往一样设置 PreparedStatement;但是不是调用 setXXX 方法设定相应参数的值,而是调用相应的 setXXXStream 方法,如清单 2 所示:
清单 2. 使用预处理语句把数据注入 Apache Derby 数据库
try {
Class.forName(driver) ;
con = DriverManager.getConnection(url);
Statement stmt = con.createStatement() ;
int counts = stmt.executeUpdate(createTableSQL) ;
con.commit() ;
File tFile = new File("bio.txt") ;
InputStream tIn = new java.io.FileInputStream(tFile);
int tFileLength = (int) tFile.length() ;
File bFile = new File("p-rbrunner.jpg") ;
int bFileLength = (int) bFile.length() ;
InputStream bIn = new java.io.FileInputStream(bFile);
PreparedStatement pstmt = con.prepareStatement(insertInfoSQL) ;
pstmt.setInt(1, 1) ;
pstmt.setBinaryStream(2, bIn, bFileLength) ;
pstmt.setAsciiStream(3, tIn, tFileLength) ;
pstmt.execute();
con.commit() ;
showResults(infoQuerySQL) ;
stmt = con.createStatement() ;
counts = stmt.executeUpdate(dropTableSQL) ;
con.commit() ;
} catch (SQLException se) {
printSQLException(se) ;
} |
 |
用于注入数据的 SQL 数据类型 虽然在本文中使用 LONG VARCHAR 及相关的数据类型,但是一名经验更丰富的 SQL 开发人员会选择使用受 JDBC 规范和 Apache Derby 数据库支持的 SQL BLOB 和 CLOB 数据类型。接下来有一篇文章将讨论这些数据类型并详细说明这些数据类型如何与 Java 流结合使用。 |
|
您可以通过创建相关的 Java 流并为 PreparedStatement 设定相应的 INPUT 参数来结合使用流与 PreparedStatement,如清单 2 所示。如完整的示例代码所示(可从 下载 部分获得全部示例代码),需要有一张适当的表。在这种情况下,首先要创建一张名为 authors 的新临时表,该表包括三列:
- 用于识别的整型值
- 用于保存图片的
pic 列
- 用于保存说明的
bio 列
后两列分别被声明为 SQL 类型 LONG VARCHAR FOR BIT DATA 和 LONG VARCHAR。通过使用这些列类型,您无需将整个流写入内存,但是如果使用 CHAR、VARCHAR 或其变种等其他 SQL 数据类型,则必须将整个流写入内存。
使用流时要遵循的另一条重要事项是需要首先正确地打开流并确定其长度。JDBC 3.0 或早期的驱动程序要求遵守此长度(注,在 JDBC 4.0 中,此限制已被放宽)。在先前的示例中,文件已经先被打开,并且把 Java InputStream 附加到了这些文件中。文件的长度将被记录,并且随后将把流和文件长度传入相应的 setXXXStream 方法。
在使用两个 Java 流插入此数据后,然后您将在删除它之前查询表。要查看得到的操作,请执行 PreparedStream.java 文件,如清单 3 所示:
清单 3. 用预处理语句注入数据
rb$ java PreparedStream
ID |PIC |BIO
------------------------------------
1 |Robert J. |[B@1eb50b
1 rows selected
|
运行此示例时,Java 代码将创建一张临时 authors 表,打开两个文件(本文的源代码附带的 bio.txt 和 p-rbrunner.jpg),通过从这两个文件读取数据将一行插入 authors 表,显示此行(它已被简写,因为后两列中有大量数据),然后删除这张表以归还数据库中的空间。通过把流与 PreparedStatement 结合使用,可以将大量复杂数据快速插入 Apache Derby 数据库。
预处理语句元数据
在先前的文章中,您了解了数据库连接和查询结果的 JDBC 元数据支持。根据 JDBC 3.0 规范,PreparedStatement 对象也被提供了可以通过 ParameterMetaData 对象访问的元数据支持。此对象公开了使 Java 程序确定 PreparedStatement 的各种特性的多种不同方法,如清单 4 所示:
清单 4. 访问预处理语句的元数据
try {
Class.forName(driver) ;
con = DriverManager.getConnection(url);
PreparedStatement pstmt = con.prepareStatement(insertInfoSQL) ;
pstmt.setInt(1, 1) ;
pstmt.setBinaryStream(2, bIn, bFileLength) ;
pstmt.setAsciiStream(3, tIn, tFileLength) ;
ParameterMetaData pmd = pstmt.getParameterMetaData();
int count = pmd.getParameterCount();
System.out.println("\nThis PreparedStatement contains " + count + " parameters.\n");
System.out.printf("%-10s|%27s|%24s|\n", "Argument","Type", "Class") ;
String line = "--------------------------------" ;
System.out.println(line + line);
for (int i=1; i <= count; i++){
System.out.printf("%-10d", i) ;
System.out.printf("%28s", pmd.getParameterTypeName(i));
System.out.printf("%25s\n", pmd.getParameterClassName(i));
}
System.out.println(line + line + "\n");
} catch (SQLException se) {
printSQLException(se) ;
} |
在本例中,首先像先前在清单 2 中所做的一样定义 PreparedStatement。但是,在这种情况下,将不执行 PreparedStatement,但是将通过在新的 PreparedStatement 上调用 getParameterMetaData() 方法来使用它访问相应的元数据。ParameterMetaData 对象支持九种不同方法用于确定 PreparedStatement 所使用参数数目,以及关于每个特定参数的详细信息,此时必须将参数数目传入相应的方法。在本例中,将使用 getParameterTypeName() 和 getParameterClassName() 方法来检索 SQL 数据类型和用于处理参数的 Java 类。
要使用 JDBC PreparedStatement 的元数据,可以执行 PreparedMetaData.java 文件,如清单 5 所示:
清单 5. 查看预处理语句元数据
rb$ java PreparedMetaData
This PreparedStatement contains 3 parameters.
Argument | Type| Class|
----------------------------------------------------------------
1 INTEGER java.lang.Integer
2 LONG VARCHAR FOR BIT DATA byte[]
3 LONG VARCHAR java.lang.String
----------------------------------------------------------------
|
Apache Derby JDBC 驱动程序将把 PreparedStatement 的参数从 Java 数据类型映射到适当的 SQL 数据类型。在这种情况下,二进制数据被处理为字节数组,而字符数据被处理为 String。您可以使用 ParameterMetaData 对象所公开的其他方法中的一些方法来扩展此示例以提供各种参数的其他详细信息,包括:
-
isNullable(),用于确定参数是否可以保存 NULL 值。
-
isSigned(),用于确定参数是否可以保存有符号的数值。
-
getPrecision(),用于确定参数可以保存的小数位数。
-
getScale(),用于确定参数在小数点后支持的小数位数。
-
getParameterType(),用于确定参数的 SQL 类型。
-
getParameterMode(),用于确定给定参数的模式。
通过正确地使用 ParameterMetaData,可以确保应用程序可以在各个版本的 Apache Derby 数据库中正确运行,并且还可以查看应用程序可以怎样调整为功能更强大的企业级数据库(如 IBM DB2 Universal Database)。
结束语
在本文中,您了解了如何更有效地使用 PreparedStatement 对象来支持更高级的查询。这包括了使用 ij 工具准备、执行和删除 PreparedStatement,它们会在构建不同方法的原型时十分有用。接下来,向您介绍了把数据注入 JDBC 应用程序的概念;在本例中,PreparedStatement 参数,用于将数据更高效地插入 Apache Derby 数据库。最后,了解了 ParameterMetaData 对象以及它可以怎样用于获得各种 PreparedStatement 参数的元数据。下一步是将您了解的所有信息打包成一个含有嵌入式 Apache Derby 数据库的 Java 应用程序。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 本文的 Derby SQL 脚本和 Java 代码 | derby13.zip | 4KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | 
|  | Robert J. Brunner 是 National Center for Supercomputing Applications 的研究科学家,也是位于 Urbana-Champaign 的伊利诺斯大学的天文学助理教授。他出版过多部著作,发表过主题广泛的文章和教程。 |
对本文的评价
|  |
Cloudscape、DB2、DB2 Universal Database、IBM 和 IBM 徽标是 IBM 在美国和/或其他国家或地区的注册商标。 Java 和所有基于 Java 的商标都是 Sun Microsystems, Inc. 在美国和/或其他国家或地区的商标。 其他公司、产品或服务的名称可能是其他公司的商标或服务标志。 IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。 |