使用准备好的陈述

原文: https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html

此页面包含以下主题:

有时使用PreparedStatement对象将 SQL 语句发送到数据库会更方便。这种特殊类型的语句来自您已经知道的更通用的类Statement

如果要多次执行Statement对象,通常会缩短执行时间以使用PreparedStatement对象。

PreparedStatement对象的主要特征是,与Statement对象不同,它在创建时会被赋予一个 SQL 语句。这样做的好处是,在大多数情况下,此 SQL 语句会立即发送到 DBMS,并在其中进行编译。因此,PreparedStatement对象不仅包含 SQL 语句,还包含已预编译的 SQL 语句。这意味着当执行PreparedStatement时,DBMS 可以只运行PreparedStatement SQL 语句而无需先编译它。

尽管PreparedStatement对象可用于没有参数的 SQL 语句,但您最常使用它们来获取带参数的 SQL 语句。使用带参数的 SQL 语句的优点是,您可以使用相同的语句,并在每次执行时为其提供不同的值。这方面的例子在以下部分中。

以下方法[CoffeesTable.updateCoffeeSales](gettingstarted.html),为每种类型的咖啡在SALES栏中存储当前销售的咖啡磅数,并更新总数每种咖啡在TOTAL栏中售出的咖啡磅数:

  1. public void updateCoffeeSales(HashMap<String, Integer> salesForWeek)
  2. throws SQLException {
  3. PreparedStatement updateSales = null;
  4. PreparedStatement updateTotal = null;
  5. String updateString =
  6. "update " + dbName + ".COFFEES " +
  7. "set SALES = ? where COF_NAME = ?";
  8. String updateStatement =
  9. "update " + dbName + ".COFFEES " +
  10. "set TOTAL = TOTAL + ? " +
  11. "where COF_NAME = ?";
  12. try {
  13. con.setAutoCommit(false);
  14. updateSales = con.prepareStatement(updateString);
  15. updateTotal = con.prepareStatement(updateStatement);
  16. for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
  17. updateSales.setInt(1, e.getValue().intValue());
  18. updateSales.setString(2, e.getKey());
  19. updateSales.executeUpdate();
  20. updateTotal.setInt(1, e.getValue().intValue());
  21. updateTotal.setString(2, e.getKey());
  22. updateTotal.executeUpdate();
  23. con.commit();
  24. }
  25. } catch (SQLException e ) {
  26. JDBCTutorialUtilities.printSQLException(e);
  27. if (con != null) {
  28. try {
  29. System.err.print("Transaction is being rolled back");
  30. con.rollback();
  31. } catch(SQLException excep) {
  32. JDBCTutorialUtilities.printSQLException(excep);
  33. }
  34. }
  35. } finally {
  36. if (updateSales != null) {
  37. updateSales.close();
  38. }
  39. if (updateTotal != null) {
  40. updateTotal.close();
  41. }
  42. con.setAutoCommit(true);
  43. }
  44. }

下面创建一个PreparedStatement对象,它接受两个输入参数:

  1. String updateString =
  2. "update " + dbName + ".COFFEES " +
  3. "set SALES = ? where COF_NAME = ?";
  4. updateSales = con.prepareStatement(updateString);

在执行PreparedStatement对象之前,必须提供值代替问号占位符(如果有)。通过调用PreparedStatement类中定义的 setter 方法之一来完成此操作。以下语句提供名为updateSalesPreparedStatement中的两个问号占位符:

  1. updateSales.setInt(1, e.getValue().intValue());
  2. updateSales.setString(2, e.getKey());

每个 setter 方法的第一个参数指定问号占位符。在此示例中,setInt指定第一个占位符,setString指定第二个占位符。

使用值设置参数后,它会保留该值,直到将其重置为其他值,或者调用方法clearParameters。使用PreparedStatement对象updateSales,以下代码片段说明在重置其中一个参数的值并使另一个参数保持相同之后重用预准备语句:

  1. // changes SALES column of French Roast
  2. //row to 100
  3. updateSales.setInt(1, 100);
  4. updateSales.setString(2, "French_Roast");
  5. updateSales.executeUpdate();
  6. // changes SALES column of Espresso row to 100
  7. // (the first parameter stayed 100, and the second
  8. // parameter was reset to "Espresso")
  9. updateSales.setString(2, "Espresso");
  10. updateSales.executeUpdate();

使用循环设置值

通过使用for循环或while循环设置输入参数的值,通常可以使编码更容易。

[CoffeesTable.updateCoffeeSales](gettingstarted.html)方法使用 for-each 循环重复设置PreparedStatement对象updateSalesupdateTotal中的值:

  1. for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
  2. updateSales.setInt(1, e.getValue().intValue());
  3. updateSales.setString(2, e.getKey());
  4. // ...
  5. }

方法[CoffeesTable.updateCoffeeSales](gettingstarted.html)采用一个参数HashMapHashMap参数中的每个元素都包含一种咖啡的名称以及当周销售的该类型咖啡的磅数。 for-each 循环遍历HashMap参数的每个元素,并在updateSalesupdateTotal中设置相应的问号占位符。

Statement对象一样,要执行PreparedStatement对象,请调用 execute 语句:executeQuery如果查询只返回一个ResultSet(例如SELECT SQL 语句),executeUpdate如果查询执行如果查询可能返回多个ResultSet对象,则不返回ResultSet(例如UPDATE SQL 语句)或execute[CoffeesTable.updateCoffeeSales](gettingstarted.html)中的PreparedStatement对象都包含UPDATE SQL 语句,因此两者都通过调用executeUpdate来执行:

  1. updateSales.setInt(1, e.getValue().intValue());
  2. updateSales.setString(2, e.getKey());
  3. updateSales.executeUpdate();
  4. updateTotal.setInt(1, e.getValue().intValue());
  5. updateTotal.setString(2, e.getKey());
  6. updateTotal.executeUpdate();
  7. con.commit();

executeUpdate用于执行updateSalesupdateTotals时,不会向executeUpdate提供参数。两个PreparedStatement对象都已包含要执行的 SQL 语句。

:在CoffeesTable.updateCoffeeSales的开头,自动提交模式设置为 false:

  1. con.setAutoCommit(false);

因此,在调用方法commit之前,不会提交任何 SQL 语句。有关自动提交模式的更多信息,请参见 Transactions

executeUpdate 方法的返回值

executeQuery返回包含发送到 DBMS 的查询结果的ResultSet对象,而executeUpdate的返回值是int值,表示已更新表的行数。例如,以下代码显示executeUpdate的返回值分配给变量n

  1. updateSales.setInt(1, 50);
  2. updateSales.setString(2, "Espresso");
  3. int n = updateSales.executeUpdate();
  4. // n = 1 because one row had a change in it

COFFEES已更新;值 50 替换Espresso行中SALES列中的值。该更新会影响表中的一行,因此n等于 1。

当方法executeUpdate用于执行 DDL(数据定义语言)语句时,例如在创建表时,它返回int值为 0.因此,在下面的代码片段中,执行使用的 DDL 语句要创建表COFFEESn的值为 0:

  1. // n = 0
  2. int n = executeUpdate(createTableCoffees);

请注意,当executeUpdate的返回值为 0 时,它可能意味着以下两种情况之一:

  • 执行的语句是一个影响零行的更新语句。
  • 执行的语句是 DDL 语句。