文档

Java™教程
隐藏目录
使用预编译语句
路径: JDBC数据库访问
课程: JDBC基础知识

使用预编译语句

本页面涵盖以下主题:

预编译语句概述

有时候,使用 PreparedStatement 对象来向数据库发送 SQL 语句更加方便。这种特殊类型的语句是从你已经了解的更通用的 Statement 类派生而来的。

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

PreparedStatement 对象的主要特点是,与 Statement 对象不同,它在创建时就被给定了一个 SQL 语句。这样做的优点是,在大多数情况下,这个 SQL 语句会立即被发送到 DBMS,然后被编译。结果是,PreparedStatement 对象不仅包含一个 SQL 语句,而且是一个已经被预编译的 SQL 语句。这意味着当执行 PreparedStatement 时,DBMS 可以直接运行 PreparedStatement 的 SQL 语句,而无需先进行编译。

尽管你可以使用 PreparedStatement 对象来处理没有参数的 SQL 语句,但你最常使用它们的情况是处理带有参数的 SQL 语句。使用带有参数的 SQL 语句的优点是,你可以多次执行相同的语句,并每次都提供不同的值。下面的部分中有一些示例。

然而,预编译语句最重要的优点是它们有助于防止 SQL 注入攻击。SQL 注入是一种通过在 SQL 语句中使用客户端提供的数据来恶意利用应用程序的技术。攻击者通过提供特制的字符串输入,欺骗 SQL 引擎执行意外的命令,从而未经授权地访问数据库以查看或操作受限数据。SQL 注入技术都利用应用程序中的一个漏洞:错误验证或未验证的字符串文字被连接到动态构建的 SQL 语句中,并被 SQL 引擎解释为代码。预编译语句始终将客户端提供的数据视为参数的内容,而不是 SQL 语句的一部分。有关更多信息,请参见 Oracle 数据库文档中的 Database PL/SQL Language ReferenceSQL 注入 部分。

下面的方法 CoffeesTable.updateCoffeeSalesSALES 列中存储本周销售的咖啡重量,并更新每种咖啡的 TOTAL 列中销售的咖啡总重量:

  public void updateCoffeeSales(HashMap<String, Integer> salesForWeek) throws SQLException {
    String updateString =
      "update COFFEES set SALES = ? where COF_NAME = ?";
    String updateStatement =
      "update COFFEES set TOTAL = TOTAL + ? where COF_NAME = ?";

    try (PreparedStatement updateSales = con.prepareStatement(updateString);
         PreparedStatement updateTotal = con.prepareStatement(updateStatement))
    
    {
      con.setAutoCommit(false);
      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("正在回滚事务");
          con.rollback();
        } catch (SQLException excep) {
          JDBCTutorialUtilities.printSQLException(excep);
        }
      }
    }
  }

创建一个PreparedStatement对象

以下代码创建一个接受两个输入参数的PreparedStatement对象:

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

为PreparedStatement参数提供值

在执行PreparedStatement对象之前,必须提供占位符的值。可以通过调用PreparedStatement类中定义的setter方法来实现。以下代码为名为updateSalesPreparedStatement对象提供了两个占位符的值:

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

这些setter方法的第一个参数指定了占位符的位置。在这个例子中,setInt指定了第一个占位符,setString指定了第二个占位符。

在为参数设置了值后,它将保持该值,直到被重置为另一个值,或者调用了clearParameters方法。使用PreparedStatement对象updateSales,以下代码片段演示了在重置其参数值后重用准备好的语句的示例:

// 将French Roast行的SALES列更改为100

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

// 将Espresso行的SALES列更改为100
// (第一个参数仍为100,第二个参数被重置为"Espresso")

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

使用循环设置值

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

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对象,调用一个执行语句:executeQuery(如果查询只返回一个ResultSet,比如SELECT SQL语句),executeUpdate(如果查询不返回ResultSet,比如UPDATE SQL语句),或execute(如果查询可能返回多个ResultSet对象)。CoffeesTable.updateCoffeeSales(HashMap<String, Integer>)中的两个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的开始处,将自动提交模式设置为false:

con.setAutoCommit(false);

因此,直到调用commit方法之前,没有SQL语句被提交。有关自动提交模式的更多信息,请参阅事务

executeUpdate方法的返回值

executeQuery返回包含发送到DBMS的查询结果的ResultSet对象不同,executeUpdate的返回值是一个int值,表示更新了多少行表。例如,下面的代码显示了将executeUpdate的返回值赋给变量n

updateSales.setInt(1, 50);
updateSales.setString(2, "Espresso");
int n = updateSales.executeUpdate();
// n = 1,因为有一行发生了更改

COFFEES被更新了;值50替换了Espresso行中SALES列的值。这个更新影响了表中的一行,所以n等于1。

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

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

需要注意的是,当executeUpdate的返回值为0时,可能有两种情况:


上一页: 从结果集中检索和修改值
下一页: 使用事务