Java 中文官方教程 2022 版(三十五)(1)

简介: Java 中文官方教程 2022 版(三十五)

从结果集中检索和修改值

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

下面的方法,CoffeesTable.viewTable,输出了 COFFEES 表的内容,并演示了 ResultSet 对象和游标的使用:

public static void viewTable(Connection con) throws SQLException {
    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
    try (Statement stmt = con.createStatement()) {
      ResultSet rs = stmt.executeQuery(query);
      while (rs.next()) {
        String coffeeName = rs.getString("COF_NAME");
        int supplierID = rs.getInt("SUP_ID");
        float price = rs.getFloat("PRICE");
        int sales = rs.getInt("SALES");
        int total = rs.getInt("TOTAL");
        System.out.println(coffeeName + ", " + supplierID + ", " + price +
                           ", " + sales + ", " + total);
      }
    } catch (SQLException e) {
      JDBCTutorialUtilities.printSQLException(e);
    }
  }

ResultSet 对象是表示数据库结果集的数据表,通常是通过执行查询数据库的语句生成的。例如,当 CoffeeTables.viewTable 方法通过 Statement 对象 stmt 执行查询时,会创建一个 ResultSet 对象 rs。请注意,可以通过实现 Statement 接口的任何对象创建 ResultSet 对象,包括 PreparedStatementCallableStatementRowSet

通过游标访问 ResultSet 对象中的数据。请注意,这个游标不是数据库游标。这个游标是指向 ResultSet 中一行数据的指针。最初,游标位于第一行之前。方法 ResultSet.next 将游标移动到下一行。如果游标位于最后一行之后,则此方法返回 false。此方法使用 while 循环重复调用 ResultSet.next 方法来迭代 ResultSet 中的所有数据。

本页涵盖以下主题:

  • ResultSet 接口
  • 从行中检索列值
  • 游标
  • 在 ResultSet 对象中更新行
  • 使用 Statement 对象进行批量更新
  • 在 ResultSet 对象中插入行

ResultSet 接口

ResultSet 接口提供了检索和操作执行查询结果的方法,ResultSet 对象可以具有不同的功能和特性。这些特性包括类型、并发性和游标保持性

ResultSet 类型

ResultSet 对象的类型确定了其功能级别在两个方面:游标如何被操作,以及对底层数据源进行的并发更改如何反映在 ResultSet 对象中。

ResultSet 对象的灵敏度由三种不同的 ResultSet 类型之一确定:

  • TYPE_FORWARD_ONLY:结果集无法滚动;其游标仅向前移动,从第一行之前到最后一行之后。结果集中包含的行取决于底层数据库如何生成结果。也就是说,它包含在查询执行时满足查询的行,或者在检索行时满足查询的行。
  • TYPE_SCROLL_INSENSITIVE:结果可以滚动;其游标可以相对于当前位置向前和向后移动,并且可以移动到绝对位置。结果集对在打开时对基础数据源进行的更改是不敏感的。它包含在查询执行时满足查询的行,或者在检索行时满足查询的行。
  • TYPE_SCROLL_SENSITIVE:结果可以滚动;其游标可以相对于当前位置向前和向后移动,并且可以移动到绝对位置。结果集反映了在结果集保持打开状态时对基础数据源所做的更改。

默认的ResultSet类型是TYPE_FORWARD_ONLY

注意:并非所有数据库和 JDBC 驱动程序都支持所有ResultSet类型。如果指定的ResultSet类型受支持,则方法DatabaseMetaData.supportsResultSetType返回true,否则返回false

ResultSet 并发性

ResultSet对象的并发性确定支持的更新功能级别。

有两个并发级别:

  • CONCUR_READ_ONLYResultSet对象不能使用ResultSet接口进行更新。
  • CONCUR_UPDATABLEResultSet对象可以使用ResultSet接口进行更新。

默认的ResultSet并发性是CONCUR_READ_ONLY

注意:并非所有的 JDBC 驱动程序和数据库都支持并发性。如果指定的并发级别由驱动程序支持,则方法DatabaseMetaData.supportsResultSetConcurrency返回true,否则返回false

方法CoffeesTable.modifyPrices演示了如何使用并发级别为CONCUR_UPDATABLEResultSet对象。

游标可保持性

调用方法Connection.commit可以关闭在当前事务期间创建的ResultSet对象。然而,在某些情况下,这可能不是期望的行为。ResultSet属性holdability使应用程序可以控制在调用commit时是否关闭ResultSet对象(游标)。

以下ResultSet常量可以提供给Connection方法createStatementprepareStatementprepareCall

  • HOLD_CURSORS_OVER_COMMITResultSet游标不会关闭;它们是可保持的:当调用commit方法时,它们保持打开状态。如果您的应用程序主要使用只读ResultSet对象,则可保持的游标可能是理想的。
  • CLOSE_CURSORS_AT_COMMIT:在调用commit方法时关闭ResultSet对象(游标)。在调用此方法时关闭游标可能会提高某些应用程序的性能。

默认的游标可保持性取决于您的 DBMS。

注意:并非所有的 JDBC 驱动程序和数据库都支持可保持和不可保持的游标。以下方法JDBCTutorialUtilities.cursorHoldabilitySupport输出ResultSet对象的默认游标保持性以及是否支持HOLD_CURSORS_OVER_COMMITCLOSE_CURSORS_AT_COMMIT

public static void cursorHoldabilitySupport(Connection conn)
    throws SQLException {
    DatabaseMetaData dbMetaData = conn.getMetaData();
    System.out.println("ResultSet.HOLD_CURSORS_OVER_COMMIT = " +
        ResultSet.HOLD_CURSORS_OVER_COMMIT);
    System.out.println("ResultSet.CLOSE_CURSORS_AT_COMMIT = " +
        ResultSet.CLOSE_CURSORS_AT_COMMIT);
    System.out.println("Default cursor holdability: " +
        dbMetaData.getResultSetHoldability());
    System.out.println("Supports HOLD_CURSORS_OVER_COMMIT? " +
        dbMetaData.supportsResultSetHoldability(
            ResultSet.HOLD_CURSORS_OVER_COMMIT));
    System.out.println("Supports CLOSE_CURSORS_AT_COMMIT? " +
        dbMetaData.supportsResultSetHoldability(
            ResultSet.CLOSE_CURSORS_AT_COMMIT));
}

从行中检索列值

ResultSet接口声明了获取器方法(例如,getBooleangetLong)用于从当前行检索列值。您可以使用列的索引号或别名或名称检索值。列索引通常更有效。列从 1 开始编号。为了最大的可移植性,应按照从左到右的顺序读取每行中的结果集列,并且每列只能读取一次。

例如,以下方法CoffeesTable.alternateViewTable,通过编号检索列值:

public static void alternateViewTable(Connection con) throws SQLException {
    String query = "select COF_NAME, SUP_ID, PRICE, SALES, TOTAL from COFFEES";
    try (Statement stmt = con.createStatement()) {
      ResultSet rs = stmt.executeQuery(query);
      while (rs.next()) {
        String coffeeName = rs.getString(1);
        int supplierID = rs.getInt(2);
        float price = rs.getFloat(3);
        int sales = rs.getInt(4);
        int total = rs.getInt(5);
        System.out.println(coffeeName + ", " + supplierID + ", " + price +
                           ", " + sales + ", " + total);
      }
    } catch (SQLException e) {
      JDBCTutorialUtilities.printSQLException(e);
    }
  }

作为获取器方法输入的字符串是不区分大小写的。当使用字符串调用获取器方法时,如果有多个列具有与字符串相同的别名或名称,则返回第一个匹配列的值。使用字符串而不是整数的选项设计用于在生成结果集的 SQL 查询中使用列别名和名称。对于在查询中明确命名的列(例如,select * from COFFEES),最好使用列号。如果使用列名,开发人员应确保它们通过使用列别名唯一地引用所需的列。列别名有效地重命名了结果集的列。要指定列别名,请在SELECT语句中使用 SQL AS子句。

适当类型的获取器方法检索每列中的值。例如,在方法CoffeeTables.viewTable中,ResultSet rs中每行的第一列是COF_NAME,存储了 SQL 类型VARCHAR的值。检索 SQL 类型VARCHAR值的方法是getString。每行中的第二列存储了 SQL 类型INTEGER的值,检索该类型值的方法是getInt

注意,虽然推荐使用方法getString来检索 SQL 类型CHARVARCHAR,但也可以使用它来检索任何基本的 SQL 类型。使用getString获取所有值可能非常有用,但也有其局限性。例如,如果用于检索数值类型,getString会将数值转换为 Java String对象,必须将该值转换回数值类型才能作为数字进行操作。在将值视为字符串处理的情况下,没有任何缺点。此外,如果要求应用程序检索除 SQL3 类型以外的任何标准 SQL 类型的值,请使用getString方法。

游标

如前所述,通过光标访问ResultSet对象中的数据,光标指向ResultSet对象中的一行。但是,当创建ResultSet对象时,光标位于第一行之前。方法CoffeeTables.viewTable通过调用ResultSet.next方法移动光标。还有其他可用于移动光标的方法:

  • next: 将光标向前移动一行。如果光标现在位于一行上,则返回true,如果光标位于最后一行之后,则返回false
  • previous: 将光标向后移动一行。如果光标现在位于一行上,则返回true,如果光标位于第一行之前,则返回false
  • first: 将光标移动到ResultSet对象中的第一行。如果光标现在位于第一行上,则返回true,如果ResultSet对象不包含任何行,则返回false
  • last:: 将光标移动到ResultSet对象中的最后一行。如果光标现在位于最后一行上,则返回true,如果ResultSet对象不包含任何行,则返回false
  • beforeFirst: 将光标定位在ResultSet对象的开头,即第一行之前。如果ResultSet对象不包含任何行,则此方法不起作用。
  • afterLast: 将光标定位在ResultSet对象的末尾,即最后一行之后。如果ResultSet对象不包含任何行,则此方法不起作用。
  • relative(int rows): 相对于当前位置移动光标。
  • absolute(int row): 将光标定位在参数row指定的行上。

请注意,ResultSet的默认灵敏度是TYPE_FORWARD_ONLY,这意味着它不能滚动;如果您的ResultSet不能滚动,则除了next之外,您不能调用任何移动光标的方法。下一节中描述的CoffeesTable.modifyPrices方法演示了如何移动ResultSet的光标。

更新ResultSet对象中的行

您不能更新默认的ResultSet对象,只能将其光标向前移动。但是,您可以创建可以滚动(光标可以向后移动或移动到绝对位置)和更新的ResultSet对象。

以下方法,CoffeesTable.modifyPrices,将每行的PRICE列乘以参数percentage

public void modifyPrices(float percentage) throws SQLException {
    try (Statement stmt =
      con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE)) {
      ResultSet uprs = stmt.executeQuery("SELECT * FROM COFFEES");
      while (uprs.next()) {
        float f = uprs.getFloat("PRICE");
        uprs.updateFloat("PRICE", f * percentage);
        uprs.updateRow();
      }
    } catch (SQLException e) {
      JDBCTutorialUtilities.printSQLException(e);
    }
  }

字段ResultSet.TYPE_SCROLL_SENSITIVE创建一个ResultSet对象,其光标可以相对于当前位置和绝对位置向前和向后移动。字段ResultSet.CONCUR_UPDATABLE创建一个可更新的ResultSet对象。查看ResultSet Javadoc 以了解可以指定的其他字段,以修改ResultSet对象的行为。

方法ResultSet.updateFloat更新了指定列(在本例中为PRICE)中光标所在行的指定float值。ResultSet包含各种更新方法,使您能够更新各种数据类型的列值。然而,这些更新方法都不会修改数据库;您必须调用方法ResultSet.updateRow来更新数据库。

使用语句对象进行批量更新

StatementPreparedStatementCallableStatement对象都有一个与之关联的命令列表。该列表可能包含用于更新、插入或删除行的语句;也可能包含 DDL 语句,如CREATE TABLEDROP TABLE。但是,它不能包含会产生ResultSet对象的语句,比如SELECT语句。换句话说,该列表只能包含产生更新计数的语句。

Statement对象在创建时关联的列表最初为空。您可以使用方法addBatch向此列表添加 SQL 命令,并使用方法clearBatch清空它。当您完成向列表添加语句时,调用方法executeBatch将它们全部发送到数据库以作为一个单元或批量执行。

例如,以下方法,CoffeesTable.batchUpdate,使用批量更新向COFFEES表添加了四行:

public void batchUpdate() throws SQLException {
    con.setAutoCommit(false);
    try (Statement stmt = con.createStatement()) {
      stmt.addBatch("INSERT INTO COFFEES " +
                    "VALUES('Amaretto', 49, 9.99, 0, 0)");
      stmt.addBatch("INSERT INTO COFFEES " +
                    "VALUES('Hazelnut', 49, 9.99, 0, 0)");
      stmt.addBatch("INSERT INTO COFFEES " +
                    "VALUES('Amaretto_decaf', 49, 10.99, 0, 0)");
      stmt.addBatch("INSERT INTO COFFEES " +
                    "VALUES('Hazelnut_decaf', 49, 10.99, 0, 0)");
      int[] updateCounts = stmt.executeBatch();
      con.commit();
    } catch (BatchUpdateException b) {
      JDBCTutorialUtilities.printBatchUpdateException(b);
    } catch (SQLException ex) {
      JDBCTutorialUtilities.printSQLException(ex);
    } finally {
      con.setAutoCommit(true);
    }
  }

以下行禁用了Connection对象con的自动提交模式,这样当调用方法executeBatch时,事务将不会自动提交或回滚。

con.setAutoCommit(false);

为了正确处理错误,您应该在开始批量更新之前始终禁用自动提交模式。

方法Statement.addBatch将一个命令添加到与Statement对象stmt关联的命令列表中。在本例中,这些命令都是INSERT INTO语句,每个语句都添加了由五个列值组成的行。列COF_NAMEPRICE的值分别是咖啡的名称和价格。每行中的第二个值为 49,因为这是供应商 Superior Coffee 的标识号。最后两个值,列SALESTOTAL的条目,都从零开始,因为尚未进行销售。(SALES是本行咖啡在本周销售的磅数;TOTAL是该咖啡所有累计销售的总量。)

以下行将添加到其命令列表中的四个 SQL 命令发送到数据库以作为批量执行:

int[] updateCounts = stmt.executeBatch();

请注意,stmt使用方法executeBatch发送插入的批处理,而不是使用方法executeUpdate,后者只发送一个命令并返回单个更新计数。数据库管理系统按照添加到命令列表的顺序执行命令,因此它将首先添加 Amaretto 的值行,然后添加 Hazelnut 的行,然后是 Amaretto decaf,最后是 Hazelnut decaf。如果所有四个命令都成功执行,数据库管理系统将按照执行顺序为每个命令返回一个更新计数。指示每个命令影响了多少行的更新计数存储在数组updateCounts中。

如果批处理中的所有四个命令都成功执行,updateCounts将包含四个值,所有这些值都为 1,因为插入会影响一行。与stmt关联的命令列表现在将为空,因为之前添加的四个命令在stmt调用方法executeBatch时已发送到数据库。您随时可以使用方法clearBatch显式清空此命令列表。

Connection.commit方法使对COFFEES表的更新批量变为永久。之前为此连接禁用了自动提交模式,因此需要显式调用此方法。

以下行启用当前Connection对象的自动提交模式。

con.setAutoCommit(true);

现在,示例中的每个语句在执行后将自动提交,并且不再需要调用方法commit

执行参数化的批量更新

还可以进行参数化的批量更新,如下面的代码片段所示,其中con是一个Connection对象:

con.setAutoCommit(false);
PreparedStatement pstmt = con.prepareStatement(
                              "INSERT INTO COFFEES VALUES( " +
                              "?, ?, ?, ?, ?)");
pstmt.setString(1, "Amaretto");
pstmt.setInt(2, 49);
pstmt.setFloat(3, 9.99);
pstmt.setInt(4, 0);
pstmt.setInt(5, 0);
pstmt.addBatch();
pstmt.setString(1, "Hazelnut");
pstmt.setInt(2, 49);
pstmt.setFloat(3, 9.99);
pstmt.setInt(4, 0);
pstmt.setInt(5, 0);
pstmt.addBatch();
// ... and so on for each new
// type of coffee
int[] updateCounts = pstmt.executeBatch();
con.commit();
con.setAutoCommit(true);

处理批量更新异常

如果批处理中的一个 SQL 语句产生结果集(通常是查询),或者批处理中的一个 SQL 语句由于其他原因未成功执行,当调用方法executeBatch时,将会收到BatchUpdateException

你不应该将查询(一个SELECT语句)添加到一批 SQL 命令中,因为方法executeBatch期望从每个成功执行的 SQL 语句中返回一个更新计数。这意味着只有返回更新计数的命令(如INSERT INTOUPDATEDELETE)或返回 0 的命令(如CREATE TABLEDROP TABLEALTER TABLE)才能成功地作为一批与executeBatch方法一起执行。

BatchUpdateException包含一个与executeBatch方法返回的数组类似的更新计数数组。在这两种情况下,更新计数与产生它们的命令的顺序相同。这告诉你批处理中有多少个命令成功执行以及它们是哪些。例如,如果五个命令成功执行,数组将包含五个数字:第一个是第一个命令的更新计数,第二个是第二个命令的更新计数,依此类推。

BatchUpdateException是从SQLException派生的。这意味着你可以使用所有SQLException对象可用的方法。以下方法,JDBCTutorialUtilities.printBatchUpdateException,打印了所有SQLException信息以及BatchUpdateException对象中包含的更新计数。因为BatchUpdateException.getUpdateCounts返回一个int数组,代码使用for循环打印每个更新计数:

public static void printBatchUpdateException(BatchUpdateException b) {
    System.err.println("----BatchUpdateException----");
    System.err.println("SQLState:  " + b.getSQLState());
    System.err.println("Message:  " + b.getMessage());
    System.err.println("Vendor:  " + b.getErrorCode());
    System.err.print("Update counts:  ");
    int[] updateCounts = b.getUpdateCounts();
    for (int i = 0; i < updateCounts.length; i++) {
      System.err.print(updateCounts[i] + "   ");
    }
  }

ResultSet对象中插入行

注意:并非所有的 JDBC 驱动程序都支持使用ResultSet接口插入新行。如果尝试插入新行而你的 JDBC 驱动程序数据库不支持此功能,将抛出SQLFeatureNotSupportedException异常。

以下方法,CoffeesTable.insertRow,通过ResultSet对象向COFFEES插入一行:

public void insertRow(String coffeeName, int supplierID, float price,
                        int sales, int total) throws SQLException {
    try (Statement stmt =
          con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_UPDATABLE))
    {      
      ResultSet uprs = stmt.executeQuery("SELECT * FROM COFFEES");
      uprs.moveToInsertRow();
      uprs.updateString("COF_NAME", coffeeName);
      uprs.updateInt("SUP_ID", supplierID);
      uprs.updateFloat("PRICE", price);
      uprs.updateInt("SALES", sales);
      uprs.updateInt("TOTAL", total);
      uprs.insertRow();
      uprs.beforeFirst();
    } catch (SQLException e) {
      JDBCTutorialUtilities.printSQLException(e);
    }
  }

此示例调用Connection.createStatement方法,带有两个参数,ResultSet.TYPE_SCROLL_SENSITIVEResultSet.CONCUR_UPDATABLE。第一个值使ResultSet对象的游标能够向前和向后移动。第二个值,ResultSet.CONCUR_UPDATABLE,如果要向ResultSet对象插入行,则需要它;它指定它可以被更新。

在 getter 方法中使用字符串的相同规定也适用于 updater 方法。

方法ResultSet.moveToInsertRow将游标移动到插入行。插入行是与可更新结果集关联的特殊行。它本质上是一个缓冲区,可以通过调用更新器方法构造新行,然后将该行插入结果集。例如,此方法调用ResultSet.updateString方法将插入行的COF_NAME列更新为Kona

方法ResultSet.insertRow将插入行的内容插入ResultSet对象和数据库中。

注意:使用ResultSet.insertRow插入一行后,应将游标移动到插入行之外的行。例如,此示例使用ResultSet.beforeFirst方法将其移动到结果集中的第一行之前。如果应用程序的另一部分使用相同的结果集且游标仍指向插入行,则可能会出现意外结果。

使用预编译语句

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

本页涵盖以下主题:

  • 预编译语句概述
  • 创建一个 PreparedStatement 对象
  • 为 PreparedStatement 参数提供值

预编译语句概述

有时,使用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 数据库文档中的数据库 PL/SQL 语言参考部分中的SQL 注入部分。

以下方法,CoffeesTable.updateCoffeeSales,将当前周内销售的咖啡磅数存储在每种咖啡的SALES列中,并更新每种咖啡的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("Transaction is being rolled back");
          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,以下代码片段演示了在重置其参数值并保持另一个参数值不变后重用准备好的语句:

// 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方法使用 for-each 循环重复设置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对象,调用一个执行语句:如果查询只返回一个ResultSet(如SELECT SQL 语句),则调用executeQuery,如果查询不返回ResultSet(如UPDATE SQL 语句),则调用executeUpdate,如果查询可能返回多个ResultSet对象,则调用executeCoffeesTable.updateCoffeeSales(HashMap)中的两个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 because one row had a change in it

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

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

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

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

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

使用事务

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

有时候你不希望一个语句生效,除非另一个语句完成。例如,当“咖啡休息时间”的老板更新每周销售的咖啡量时,老板还希望更新迄今为止的总销售量。然而,每周销售量和总销售量应该同时更新;否则,数据将不一致。确保两个动作都发生或两个动作都不发生的方法是使用事务。事务是一组一个或多个语句,作为一个单元执行,因此要么所有语句都执行,要么所有语句都不执行。

本页涵盖以下主题

  • 禁用自动提交模式
  • 提交事务
  • 使用事务保持数据完整性
  • 设置和回滚保存点
  • 释放保存点
  • 何时调用 rollback 方法

禁用自动提交模式

当创建连接时,它处于自动提交模式。这意味着每个单独的 SQL 语句都被视为一个事务,并在执行后立即自动提交。(更准确地说,默认情况下,SQL 语句在完成时提交,而不是在执行时。当所有结果集和更新计数都被检索时,语句完成。然而,在几乎所有情况下,语句在执行后立即完成,因此提交。)

允许将两个或多个语句分组为一个事务的方法是禁用自动提交模式。在以下代码中演示了这一点,其中con是一个活动连接:

con.setAutoCommit(false);

Java 中文官方教程 2022 版(三十五)(2)https://developer.aliyun.com/article/1488061

相关文章
|
7天前
|
JavaScript NoSQL Java
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
152 96
接替此文【下篇-服务端+后台管理】优雅草蜻蜓z系统JAVA版暗影版为例-【蜻蜓z系列通用】-2025年全新项目整合搭建方式-这是独立吃透代码以后首次改变-独立PC版本vue版搭建教程-优雅草卓伊凡
|
28天前
|
消息中间件 Java 数据库
自研Java框架 Sunrays-Framework使用教程「博客之星」
### Sunrays-Framework:助力高效开发的Java微服务框架 **Sunrays-Framework** 是一款基于 Spring Boot 构建的高效微服务开发框架,深度融合了 Spring Cloud 生态中的核心技术组件。它旨在简化数据访问、缓存管理、消息队列、文件存储等常见开发任务,帮助开发者快速构建高质量的企业级应用。 #### 核心功能 - **MyBatis-Plus**:简化数据访问层开发,提供强大的 CRUD 操作和分页功能。 - **Redis**:实现高性能缓存和分布式锁,提升系统响应速度。 - **RabbitMQ**:可靠的消息队列支持,适用于异步
自研Java框架 Sunrays-Framework使用教程「博客之星」
|
30天前
|
Java 数据库连接 数据处理
探究Java异常处理【保姆级教程】
Java 异常处理是确保程序稳健运行的关键机制。它通过捕获和处理运行时错误,避免程序崩溃。Java 的异常体系以 `Throwable` 为基础,分为 `Error` 和 `Exception`。前者表示严重错误,后者可细分为受检和非受检异常。常见的异常处理方式包括 `try-catch-finally`、`throws` 和 `throw` 关键字。此外,还可以自定义异常类以满足特定需求。最佳实践包括捕获具体异常、合理使用 `finally` 块和谨慎抛出异常。掌握这些技巧能显著提升程序的健壮性和可靠性。
47 4
|
29天前
|
存储 移动开发 算法
【潜意识Java】Java基础教程:从零开始的学习之旅
本文介绍了 Java 编程语言的基础知识,涵盖从简介、程序结构到面向对象编程的核心概念。首先,Java 是一种高级、跨平台的面向对象语言,支持“一次编写,到处运行”。接着,文章详细讲解了 Java 程序的基本结构,包括包声明、导入语句、类声明和 main 方法。随后,深入探讨了基础语法,如数据类型、变量、控制结构、方法和数组。此外,还介绍了面向对象编程的关键概念,例如类与对象、继承和多态。最后,针对常见的编程错误提供了调试技巧,并总结了学习 Java 的重要性和方法。适合初学者逐步掌握 Java 编程。
52 1
|
2月前
|
移动开发 前端开发 Java
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
JavaFX是Java的下一代图形用户界面工具包。JavaFX是一组图形和媒体API,我们可以用它们来创建和部署富客户端应用程序。 JavaFX允许开发人员快速构建丰富的跨平台应用程序,允许开发人员在单个编程接口中组合图形,动画和UI控件。本文详细介绍了JavaFx的常见用法,相信读完本教程你一定有所收获!
1394 1
Java最新图形化界面开发技术——JavaFx教程(含UI控件用法介绍、属性绑定、事件监听、FXML)
|
2月前
|
NoSQL Java 关系型数据库
Liunx部署java项目Tomcat、Redis、Mysql教程
本文详细介绍了如何在 Linux 服务器上安装和配置 Tomcat、MySQL 和 Redis,并部署 Java 项目。通过这些步骤,您可以搭建一个高效稳定的 Java 应用运行环境。希望本文能为您在实际操作中提供有价值的参考。
184 26
|
29天前
|
前端开发 Java 开发工具
Git使用教程-将idea本地Java等文件配置到gitte上【保姆级教程】
本内容详细介绍了使用Git进行版本控制的全过程,涵盖从本地仓库创建到远程仓库配置,以及最终推送代码至远程仓库的步骤。
38 0
|
2月前
|
安全 Java 编译器
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
Kotlin教程笔记(27) -Kotlin 与 Java 共存(二)
|
2月前
|
Java 开发工具 Android开发
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
Kotlin教程笔记(26) -Kotlin 与 Java 共存(一)
|
3月前
|
Java 编译器 Android开发
Kotlin教程笔记(28) -Kotlin 与 Java 混编
Kotlin教程笔记(28) -Kotlin 与 Java 混编
59 2

热门文章

最新文章