使用准备好的陈述
原文: 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
栏中售出的咖啡磅数:
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 。
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 语句。