使用准备好的语句

此页面包含以下主题:

准备好的报表概述

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

如果要多次执行Statement对象,通常可以减少使用PreparedStatement对象的执行时间。

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

尽管PreparedStatement对象可以用于不带参数的 SQL 语句,但是您可能最常将它们用于带有参数的 SQL 语句。使用带有参数的 SQL 语句的优点在于,您可以使用同一条语句,并在每次执行时为其提供不同的值。以下各节为示例。

以下方法CoffeesTable.updateCoffeeSales将每种类型的咖啡在本周内的销售磅数存储在SALES列中,并更新每种类型的TOTAL列中的销售磅数总数:

public void updateCoffeeSales(HashMap<String, Integer> salesForWeek)
    throws SQLException {

    PreparedStatement updateSales = null;
    PreparedStatement updateTotal = null;

    String updateString =
        "update " + dbName + ".COFFEES " +
        "set SALES = ? where COF_NAME = ?";

    String updateStatement =
        "update " + dbName + ".COFFEES " +
        "set TOTAL = TOTAL + ? " +
        "where COF_NAME = ?";

    try {
        con.setAutoCommit(false);
        updateSales = con.prepareStatement(updateString);
        updateTotal = con.prepareStatement(updateStatement);

        for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {
            updateSales.setInt(1, e.getValue().intValue());
            updateSales.setString(2, e.getKey());
            updateSales.executeUpdate();
            updateTotal.setInt(1, e.getValue().intValue());
            updateTotal.setString(2, e.getKey());
            updateTotal.executeUpdate();
            con.commit();
        }
    } catch (SQLException e ) {
        JDBCTutorialUtilities.printSQLException(e);
        if (con != null) {
            try {
                System.err.print("Transaction is being rolled back");
                con.rollback();
            } catch(SQLException excep) {
                JDBCTutorialUtilities.printSQLException(excep);
            }
        }
    } finally {
        if (updateSales != null) {
            updateSales.close();
        }
        if (updateTotal != null) {
            updateTotal.close();
        }
        con.setAutoCommit(true);
    }
}

创建 PreparedStatement 对象

下面创建一个带有两个 Importing 参数的PreparedStatement对象:

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

为 PreparedStatement 参数提供值

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

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

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

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

// changes SALES column of French Roast
//row to 100

updateSales.setInt(1, 100);
updateSales.setString(2, "French_Roast");
updateSales.executeUpdate();

// changes SALES column of Espresso row to 100
// (the first parameter stayed 100, and the second
// parameter was reset to "Espresso")

updateSales.setString(2, "Espresso");
updateSales.executeUpdate();

使用循环设置值

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

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

for (Map.Entry<String, Integer> e : salesForWeek.entrySet()) {

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

    // ...
}

方法CoffeesTable.updateCoffeeSales接受一个参数HashMapHashMap参数中的每个元素都包含一种咖啡的名称以及本周售出的这种咖啡的磅数。 for-each 循环遍历HashMap参数的每个元素,并在updateSalesupdateTotal中设置适当的问号占位符。

执行 PreparedStatement 对象

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

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

updateTotal.setInt(1, e.getValue().intValue());
updateTotal.setString(2, e.getKey());
updateTotal.executeUpdate();
con.commit();

用于执行updateSalesupdateTotals的参数不会提供给executeUpdate;两个PreparedStatement对象已经包含要执行的 SQL 语句。

注意 :在CoffeesTable.updateCoffeeSales的开头,自动提交 Pattern 设置为 false:

con.setAutoCommit(false);

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

executeUpdate 方法的返回值

executeQuery返回包含发送到 DBMS 的查询结果的ResultSet对象,而executeUpdate的返回值是int值,该值指示表中有多少行被更新。例如,以下代码显示了将executeUpdate的返回值分配给变量n

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

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

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

// n = 0
int n = executeUpdate(createTableCoffees);

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

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

  • 执行的语句是 DDL 语句。