# 使用准备好的陈述

> 原文： [https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html](https://docs.oracle.com/javase/tutorial/jdbc/basics/prepared.html)

此页面包含以下主题：

*   [准备陈述概述](#overview_ps)
*   [创建 PreparedStatement 对象](#create_ps)
*   [为 PreparedStatement 参数提供值](#supply_values_ps)

有时使用`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`栏中售出的咖啡磅数：

```
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`对象，它接受两个输入参数：

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

```

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

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

```

每个 setter 方法的第一个参数指定问号占位符。在此示例中，`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`循环设置输入参数的值，通常可以使编码更容易。

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

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

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

    // ...
}

```

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

与`Statement`对象一样，要执行`PreparedStatement`对象，请调用 execute 语句：`executeQuery`如果查询只返回一个`ResultSet`（例如`SELECT` SQL 语句），`executeUpdate`如果查询执行如果查询可能返回多个`ResultSet`对象，则不返回`ResultSet`（例如`UPDATE` SQL 语句）或`execute`。 `[CoffeesTable.updateCoffeeSales](gettingstarted.html) `中的`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();

```

当`executeUpdate`用于执行`updateSales`和`updateTotals`时，不会向`executeUpdate`提供参数。两个`PreparedStatement`对象都已包含要执行的 SQL 语句。

**注**：在`CoffeesTable.updateCoffeeSales`的开头，自动提交模式设置为 false：

```
con.setAutoCommit(false);

```

因此，在调用方法`commit`之前，不会提交任何 SQL 语句。有关自动提交模式的更多信息，请参见 [Transactions](transactions.html) 。

### 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 语句要创建表`COFFEES`，`n`的值为 0：

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

```

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

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