内容


用 Apache Derby 进行开发 —— 取得节节胜利

用 Apache Derby 进行 Java 数据库开发,第 5 部分

高级预处理语句

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 用 Apache Derby 进行开发 —— 取得节节胜利

敬请期待该系列的后续内容。

此内容是该系列的一部分:用 Apache Derby 进行开发 —— 取得节节胜利

敬请期待该系列的后续内容。

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 注入数据

先前,您使用了 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) ;
    }

您可以通过创建相关的 Java 流并为 PreparedStatement 设定相应的 INPUT 参数来结合使用流与 PreparedStatement,如清单 2 所示。如完整的示例代码所示(可从 下载 部分获得全部示例代码),需要有一张适当的表。在这种情况下,首先要创建一张名为 authors 的新临时表,该表包括三列:

  • 用于识别的整型值
  • 用于保存图片的 pic
  • 用于保存说明的 bio

后两列分别被声明为 SQL 类型 LONG VARCHAR FOR BIT DATALONG VARCHAR。通过使用这些列类型,您无需将整个流写入内存,但是如果使用 CHARVARCHAR 或其变种等其他 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 应用程序。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Open source, Java technology
ArticleID=263721
ArticleTitle=用 Apache Derby 进行开发 —— 取得节节胜利: 用 Apache Derby 进行 Java 数据库开发,第 5 部分
publish-date=06072007