13. Statements(语句)

本节介绍 Statement 接口及其子类 PreparedStatement 和 CallableStatement。同时,还描述了相关主题,包括转义语法、性能提示和自动生成的键。

13.1 Statement 接口

Statement 接口定义了执行不包含参数标记的 SQL 语句的方法。PreparedStatement 接口添加了用于设置输入参数的方法,CallableStatement 接口添加了用于检索从存储过程返回的输出参数值的方法。

:除非另有说明,否则本规范中对 Statement 接口的任何引用都包括其子类 PreparedStatement 和 CallableStatement。

13.1.1 创建语句

Statement 对象是通过 Connection 对象来创建的,以下是 CODE EXAMPLE 13-1 示例展示了这个过程.

  1. Connection conn = dataSource.getConnection(user, passwd);
  2. Statement stmt = conn.createStatement()

CODE EXAMPLE 13-1 Creating a Statement object

每个 Connection 对象可以创建多个 Statement 对象,这些对象可以在程序中并发使用。CODE EXAMPLE 13-2 示例展示了这个过程.

  1. // get a connection from the DataSource object ds
  2. Connection conn = ds.getConnection(user, passwd);
  3. // create two instances of Statement
  4. Statement stmt1 = conn.createStatement();
  5. Statement stmt2 = conn.createStatement();

CODE EXAMPLE 13-2 Creating multiple Statement objects from a single connection

13.1.1.1 设置 ResultSet 特性

可以使用其他构造函数来设置 Statement 生成的结果集的类型和并发性、并发性和可保持性。有关 ResultSet 接口界面的更多信息,请参见第15章“结果集”。

代码示例 CODE EXAMPLE 13-3 创建一个 Statement 对象,该对象返回可滚动的结果集,这些结果集对 ResultSet 对象打开时所做的更改不敏感,可以更新,并且在调用 commit 时不会关闭 ResultSet 对象。

  1. Connection conn = ds.getConnection(user, passwd);
  2. Statement stmt = conn.createStatement(
  3. ResultSet.TYPE_SCROLL_INSENSITIVE,
  4. ResultSet.CONCUR_UPDATABLE,
  5. ResultSet.HOLD_CURSORS_OVER_COMMIT);

CODE EXAMPLE 13-3 Creating a scrollable, insensitive, updatable result set that stays open after the method commit is called

13.1.2 执行 Statement 对象

用于执行 Statement 对象的方法取决于正在执行的 SQL 语句的类型。如果 Statement 对象表示 SQL 查询返回 ResultSet 对象,应该使用 executeQuery 方法。如果已知 SQL 是DDL 语句或返回更新计数的 DML 语句,则应使用方法 executeUpdate。如果未知 SQL 语句的类型,则应使用方法 execute。

13.1.2.1 返回 ResultSet 对象

CODE EXAMPLE 13-4 展示了执行 SQL 返回 ResultSet 对象:

  1. Statement stmt = conn.createStatement();
  2. ResultSet rs = stmt.executeQuery(“select TITLE, AUTHOR, ISBN " +
  3. "from BOOKLIST”);
  4. while (rs.next()){
  5. ...
  6. }

CODE EXAMPLE 13-4 Executing a Statement object that returns a ResultSet object

如果执行 SQL 没返回 ResultSet 对象,则 executeQuery 方法会抛出一个 SQLException 异常。

13.1.2.2 返回更新数

在 CODE EXAMPLE 13-5 示例中,SQL 语句执行后返回了 Data Manipulation Language(DML)语句更新数据所影响的条数。如果语句没有返回,则返回 0:

  1. Statement stmt = conn.createStatement();
  2. int rows = stmt.executeUpdate(“update STOCK set ORDER = Y " +
  3. "where SUPPLY = 0”);
  4. if (rows > 0) {
  5. ...
  6. }

CODE EXAMPLE 13-5 Executing a Statement object that returns an update count

如果 SQL 语句返回的是 ResultSet,则会抛出 SQLException。

:如果数据库支持返回可能超过 Integer.MAX_VALUE 的更新数,请使用 executeLargeUpdate 方法。

13.1.2.3 返回未知或多个结果

如果有多个结果,或者如果在运行时才知道 Statement 对象返回的结果的类型或数量,则应使用 Statement 对象的 execute 方法。方法 getMoreResults、getUpdateCount 和 getResultSet 可用于检索所有结果。

:如果数据库支持返回可能超过 Integer.MAX_VALUE 的更新数,请使用 executeLargeUpdate 方法。

如果第一个结果是 ResultSet 对象,则 execute 方法返回true,如果是更新计数,则返回 false。

当方法 execute 返回 true 时,调用方法 getResultSet 以检索 ResultSet 对象。当 execute 返回 false 时,该方法 getUpdateCount 返回一个 int。如果此数字大于或等于零,则表示该语句返回的更新计数。如果为-1,则表示没有更多结果。

如果返回多个结果,则可以调用方法 getMoreResults 以获取下一个结果。与 execute 方法一样,如果下一个结果是 ResultSet 对象,则 getMoreResults 将返回 true;如果下一个结果是更新计数或者没有更多结果可用,则返回 false。

CODE EXAMPLE 13-6 展示了如何从 Statment 对象获取所有的结果。.

  1. Statement stmt = conn.createStatement();
  2. boolean retval = cstmt.execute(sql_queries);
  3. ResultSet rs;
  4. int count;
  5. do {
  6. if (retval == false) {
  7. count = stmt.getUpdateCount();
  8. if (count == -1) {
  9. // no more results
  10. break;
  11. } else {
  12. // process update count
  13. }
  14. } else { // ResultSet
  15. rs = stmt.getResultSet();
  16. // process ResultSet
  17. }
  18. retval = stmt.getMoreResults();
  19. while (true);

CODE EXAMPLE 13-6 Executing a statement that returns multiple results

默认情况下,对方法 getMoreResults 的每次调用都会关闭方法 getResultSet 返回的任何先前 ResultSet 对象。但是,该方法 getMoreResults 可以使用一个参数来指定是否应关闭getResultSet 返回的 ResultSet 对象。 Statement 接口定义可以提供给方法 getMoreResults 的三个常量:

  • CLOSE_CURRENT_RESULT - 指示在返回下一个 ResultSet 对象时应关闭当前 ResultSet 对象
  • KEEP_CURRENT_RESULT - 指示在返回下一个 ResultSet 对象时不应关闭当前 ResultSet 对象
  • CLOSE_ALL_RESULTS - 表示在返回下一个结果时应关闭任何保持打开的 ResultSet 对象

如果当前结果是更新计数而不是 ResultSet 对象,则忽略传递给 getMoreResults 的任何参数。

要确定驱动程序是否实现此功能,应用程序可以调用 DatabaseMetaData 方法 supportsMultipleOpenResults。

  1. ResultSet rs1 = stmt.getResultSet();
  2. rs1.next();
  3. ...
  4. retval = stmt.getMoreResults(Statement.KEEP_CURRENT_RESULT);
  5. if (retval == true) {
  6. ResultSet rs2 = stmt.getResultSet();
  7. rs2.next();
  8. ...
  9. rs1.next();
  10. }
  11. retval = stmt.getMoreResults(Statement.CLOSE_ALL_RESULTS);
  12. ...

CODE EXAMPLE 13-7 Keeping multiple results from a Statement object open

13.1.3 限制 Statement 对象的执行时间

setQueryTimeout 方法可用于指定 JDBC 驱动程序尝试取消正在运行的语句之前的最短时间。JDBC 驱动程序必须将此限制应用于 execute、executeBatch、executeQuery 和 executeUpdate 方法。一旦数据源有机会处理在终止正在运行的命令的请求时,将向客户端抛出 SQLException,并且在不重新执行 Statement 的情况下,不会对先前运行的命令进行其他处理。

:某些 JDBC 驱动程序实现也可能将此限制应用于 ResultSet 方法。有关详细信息,请参阅驱动程序供应商文 :在 Statement 批处理的情况下,实现定义了超时是否应用于通过 addBatch 方法或由 executeBatch 方法调用的整批 SQL 命令。

13.1.4 关闭 Statement 对象

应用程序调用 Statement.close 方法来指示它已完成处理语句。关闭创建它们的连接时,将关闭所有 Statement 对象。但是,应用程序在完成处理后立即关闭语句是一种很好的编码实践。这允许语句正在使用的任何外部资源立即释放。

关闭 Statement 对象将关闭并使该 Statement 对象生成的 ResultSet 的任何实例无效。在垃圾收集再次运行之前,ResultSet 对象所拥有的资源可能不会被释放,因此最好在不再需要ResultSet 对象时显式关闭它们。

一旦 Statement 被关闭,除了 isClosed 或 close 方法之外,任何访问其任何方法的尝试都将导致抛出 SQLException。

关于关闭 Statement 对象的这些注释也适用于 PreparedStatement 和 CallableStatement 对象。

13.2 PreparedStatement 接口

PreparedStatement 接口扩展了 Statement,添加了为语句中包含的参数标记设置值的功能。

PreparedStatement 对象表示可以准备或预编译的 SQL 语句,以便执行一次然后执行多次。 参数标记(由SQL字符串中的“?”表示)用于指定语句的输入值,这些值可能在运行时发生变化。

13.2.1 创建 PreparedStatement 对象

PreparedStatement 的实例以与 Statement 对象相同的方式创建,除了在创建语句时提供 SQL 命令:

  1. Connection conn = ds.getConnection(user, passwd);
  2. PreparedStatement ps = conn.prepareStatement(“INSERT INTO BOOKLIST" +
  3. "(AUTHOR, TITLE, ISBN) VALUES (?, ?, ?)”);

CODE EXAMPLE 13-8 Creating a PreparedStatement object with three placeholder markers

13.2.1.1 设置 ResultSet 特性

与 createStatement 一样,方法 prepareStatement 定义了一个构造函数,该构造函数可用于指定由该 PreparedStatement 生成的结果集的特征。

  1. Connection conn = ds.getConnection(user, passwd);
  2. PreparedStatement ps = conn.prepareStatement(
  3. SELECT AUTHOR, TITLE FROM BOOKLIST WHERE ISBN = ?”,
  4. ResultSet.TYPE_FORWARD_ONLY,
  5. ResultSet.CONCUR_UPDATABLE);

CODE EXAMPLE 13-9 Creating a PreparedStatement object that returns forward only,updatable result sets

13.2.2 设置参数

PreparedStatement 接口定义 setter 方法,这些方法用于替换预编译 SQL 字符串中每个参数标记的值。 方法的名称遵循“set”模式。

例如,方法 setString 用于指定期望字符串的参数标记的值。这些 setter 方法中的每一个都至少采用两个参数。第一个始终是一个 int,等于要设置的参数的序号位置,从1开始。第二个和任何剩余参数指定要分配给参数的值。

  1. PreparedStatement ps = conn.prepareStatement(“INSERT INTO BOOKLIST" +
  2. "(AUTHOR, TITLE, ISBN) VALUES (?, ?, ?)”);
  3. ps.setString(1, Zamiatin, Evgenii”);
  4. ps.setString(2, We”);
  5. ps.setLong(3, 140185852L);

CODE EXAMPLE 13-10 Setting parameters in a PreparedStatement object

必须为 PreparedStatement 对象中的每个参数标记提供一个值,然后才能执行它。如果没有为参数标记提供值,则用于执行 PreparedStatement 对象(executeQuery、executeUpdate 和 execute)的方法将抛出 SQLException。

为 PreparedStatement 对象的参数标记设置的值在执行时不会重置。可以调用 clearParameters 方法来显式清除已设置的值。使用不同的值设置参数将使用新值替换先前的值。

:如果在执行 PreparedStatement 对象时,JDBC 驱动程序通过方法 setAsciiStream 读取为参数标记设置的值,setBinaryStream、setCharacterStream、setNCharacterStream 或 setUnicodeStream,必须在下次执行之前重置这些参数,否则将抛出 SQLException。

:对于任何给定的 Statement,应用程序不应在调用 setXXX 方法之后以及在调用后续的 execute、executeQuery、executeUpdate、executeBatch 或 clearParameters 方法之前修改传递给 setXXX 方法的 value 参数。 应用程序可以修改该值执行后调用参数,执行 execute、executeQuery、executeUpdate、executeBatch 或 clearParameters 方法,如果有后续的 setXXX 方法调用覆盖之前的值或者如果不重用 Statement。 不遵守此限制可能会导致不可预测的行为。

13.2.2.1 类型转换

PreparedStatement setter 方法中指定的数据类型是 Java 编程语言中的数据类型。JDBC 驱动程序负责将其映射到相应的 JDBC 类型(java.sql.Types 中定义的 SQL 类型之一),以便它是发送到数据源的适当类型。 默认映射在附录B表B-2中指定。

13.5 性能调整

Statement 接口有两个方法可以用来调整 JDBC 驱动性能:setFetchDirection 和 setFetchSize。这两个方法的传值会影响所生成的结果集。ResultSet 接口中的两个同名方法也可以用于调整结果集本身。

如果通过这两个方法设置的值不合适,那么该设置值可能会被驱动忽略。

getFetchDirection 和 getFetchSize 方法返回当前值。如果在调用 setFetchDirection 和 setFetchSize 之前调用这两个get方法,那么方法将会返回默认值,具体默认值由驱动决定。