从结果集中检索和修改值
以下方法CoffeesTable.viewTable
输出COFFEES
表的内容,并演示ResultSet
对象和游标的用法:
public static void viewTable(Connection con, String dbName)
throws SQLException {
Statement stmt = null;
String query =
"select COF_NAME, SUP_ID, PRICE, " +
"SALES, TOTAL " +
"from " + dbName + ".COFFEES";
try {
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 + "\t" + supplierID +
"\t" + price + "\t" + sales +
"\t" + total);
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
if (stmt != null) { stmt.close(); }
}
}
ResultSet
对象是代表数据库结果集的数据表,通常通过执行查询数据库的语句来生成。例如,CoffeeTables.viewTable
方法在通过Statement
对象stmt
执行查询时会创建ResultSet
,rs
。请注意,可以通过实现Statement
interface的任何对象(包括PreparedStatement
,CallableStatement
和RowSet
)来创建ResultSet
对象。
您可以通过游标访问ResultSet
对象中的数据。请注意,此游标不是数据库游标。该光标是一个指针,指向ResultSet
中的一行数据。最初,光标位于第一行之前。方法ResultSet.next
将光标移动到下一行。如果光标位于最后一行之后,则此方法返回false
。此方法使用while
循环重复调用ResultSet.next
方法,以迭代ResultSet
中的所有数据。
此页面包含以下主题:
ResultSet Interface
ResultSet
interface提供了检索和处理已执行查询结果的方法,而ResultSet
对象可以具有不同的功能和特性。这些 Feature 是类型,并发性和游标* holdability *。
ResultSet Types
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 Concurrency
ResultSet
对象的并发确定支持什么级别的更新功能。
有两个并发级别:
-
CONCUR_READ_ONLY
:无法使用ResultSet
interface更新ResultSet
对象。 -
CONCUR_UPDATABLE
:可以使用ResultSet
interface更新ResultSet
对象。
默认的ResultSet
并发是CONCUR_READ_ONLY
。
注意 :并非所有的 JDBC 驱动程序和数据库都支持并发。如果驱动程序支持指定的并发级别,则方法DatabaseMetaData.supportsResultSetConcurrency
返回true
,否则返回false
。
方法CoffeesTable.modifyPrices
演示如何使用并发级别为CONCUR_UPDATABLE
的ResultSet
对象。
Cursor Holdability
调用方法Connection.commit
可以关闭在当前事务期间创建的ResultSet
对象。但是,在某些情况下,这可能不是所需的行为。 ResultSet
属性* holdability *使应用程序可以控制在调用 commit 时是否关闭ResultSet
对象(光标)。
以下ResultSet
常量可以提供给Connection
方法createStatement
,prepareStatement
和prepareCall
:
-
HOLD_CURSORS_OVER_COMMIT
:ResultSet
游标未关闭;它们是* holdable *的:它们在调用方法commit
时保持打开状态。如果您的应用程序主要使用只读的ResultSet
对象,则可保持游标可能是理想的选择。 -
CLOSE_CURSORS_AT_COMMIT
:ResultSet
对象(光标)在调用commit
方法时关闭。调用此方法时关闭游标可以提高某些应用程序的性能。
默认的游标可保留性取决于您的 DBMS。
注意 :并非所有的 JDBC 驱动程序和数据库都支持可保留和不可保留的游标。以下方法JDBCTutorialUtilities.cursorHoldabilitySupport
输出ResultSet
对象的默认游标可保持性以及是否支持HOLD_CURSORS_OVER_COMMIT
和CLOSE_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
interface声明用于从当前行中检索列值的 getter 方法(例如getBoolean
和getLong
)。您可以使用列的索引号或列的别名或名称来检索值。列索引通常更有效。列从 1 开始编号。为实现最大的可移植性,应按从左到右的 Sequences 读取每一行中的结果集列,并且每一列只能读取一次。
例如,以下方法CoffeesTable.alternateViewTable
按数字检索列值:
public static void alternateViewTable(Connection con)
throws SQLException {
Statement stmt = null;
String query =
"select COF_NAME, SUP_ID, PRICE, " +
"SALES, TOTAL from COFFEES";
try {
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 + "\t" + supplierID +
"\t" + price + "\t" + sales +
"\t" + total);
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
if (stmt != null) { stmt.close(); }
}
}
用作 getter 方法 Importing 的字符串 不区分大小写。当使用字符串 调用 getter 方法并且多个列具有与该字符串 相同的别名或名称时,将返回第一个匹配列的值。设计用于在生成结果集的 SQL 查询中使用列别名和名称时使用字符串 而不是整数的选项。对于在查询中未显式命名的列(例如select * from COFFEES
),最好使用列号。如果使用了列名,则开发人员应保证使用列别名唯一地引用了预期的列。列别名有效地重命名结果集的列。若要指定列别名,请使用SELECT
语句中的 SQL AS
子句。
适当类型的 getter 方法检索每一列中的值。例如,在方法CoffeeTables.viewTable
中,ResultSet
rs
的每一行中的第一列是COF_NAME
,它存储 SQL 类型VARCHAR
的值。检索 SQL 类型VARCHAR
的值的方法是getString
。每行的第二列存储一个 SQL 类型INTEGER
的值,而检索该类型的值的方法是getInt
。
请注意,尽管建议使用方法getString
检索 SQL 类型CHAR
和VARCHAR
,但是可以使用它检索任何基本的 SQL 类型。使用getString
获取所有值可能非常有用,但是也有其局限性。例如,如果将其用于检索数字类型,则getString
会将数字值转换为 Java String
对象,并且必须先将该值转换回数字类型,然后才能将其用作数字。无论如何,在将该值视为字符串 的情况下,没有任何缺点。此外,如果您希望应用程序检索 SQL3 类型以外的任何标准 SQL 类型的值,请使用getString
方法。
Cursors
如前所述,您可以通过游标访问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 {
Statement stmt = null;
try {
stmt = con.createStatement();
stmt = con.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE,
ResultSet.CONCUR_UPDATABLE);
ResultSet uprs = stmt.executeQuery(
"SELECT * FROM " + dbName + ".COFFEES");
while (uprs.next()) {
float f = uprs.getFloat("PRICE");
uprs.updateFloat( "PRICE", f * percentage);
uprs.updateRow();
}
} catch (SQLException e ) {
JDBCTutorialUtilities.printSQLException(e);
} finally {
if (stmt != null) { stmt.close(); }
}
}
字段ResultSet.TYPE_SCROLL_SENSITIVE
创建一个ResultSet
对象,该对象的光标可以相对于当前位置和绝对位置向前和向后移动。字段ResultSet.CONCUR_UPDATABLE
创建一个可以更新的ResultSet
对象。有关可以指定用来修改ResultSet
对象行为的其他字段,请参见ResultSet
Javadoc。
方法ResultSet.updateFloat
更新指定的列(在此示例中,使用光标所在行中的指定float
值更新PRICE
。ResultSet
包含各种更新程序方法,这些方法使您可以更新各种数据类型的列值。但是,这些更新程序都不方法修改数据库;您必须调用ResultSet.updateRow
方法来更新数据库。
使用语句对象进行批处理更新
Statement
,PreparedStatement
和CallableStatement
对象具有与其关联的命令列表。该列表可能包含用于更新,插入或删除行的语句;并且还可能包含 DDL 语句,例如CREATE TABLE
和DROP TABLE
。但是,它不能包含将产生ResultSet
对象的语句,例如SELECT
语句。换句话说,列表只能包含产生更新计数的语句。
该列表在创建时与Statement
对象关联,最初为空。您可以使用addBatch
方法将 SQL 命令添加到此列表,并使用clearBatch
方法将其清空。将语句添加到列表中后,请调用executeBatch
方法将其全部发送到数据库以作为一个单元或批处理执行。
例如,以下方法CoffeesTable.batchUpdate
使用批处理更新将四行添加到COFFEES
表中:
public void batchUpdate() throws SQLException {
Statement stmt = null;
try {
this.con.setAutoCommit(false);
stmt = this.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();
this.con.commit();
} catch(BatchUpdateException b) {
JDBCTutorialUtilities.printBatchUpdateException(b);
} catch(SQLException ex) {
JDBCTutorialUtilities.printSQLException(ex);
} finally {
if (stmt != null) { stmt.close(); }
this.con.setAutoCommit(true);
}
}
下一行为Connection
对象 con 禁用了自动提交 Pattern,以便在调用方法executeBatch
时不会自动提交或回滚事务。
this.con.setAutoCommit(false);
为了进行正确的错误处理,应始终在开始批量更新之前始终禁用自动提交 Pattern。
方法Statement.addBatch
将命令添加到与Statement
对象stmt
关联的命令列表中。在此示例中,这些命令均为INSERT INTO
语句,每个命令添加一行包含五个列值的行。列COF_NAME
和PRICE
的值分别是咖啡的名称和价格。每行中的第二个值是 49,因为这是供应商 Superior Coffee 的标识号。最后两个值,即SALES
和TOTAL
列的条目都开始为零,因为还没有销售。 (SALES
是该行本周售出的咖啡磅数; TOTAL
是此咖啡的所有累计销售量之和.)
下面的行将添加到其命令列表中的四个 SQL 命令发送到数据库中,以作为批处理执行:
int [] updateCounts = stmt.executeBatch();
请注意,stmt
使用方法executeBatch
发送批量插入,而不是方法executeUpdate
,该方法仅发送一个命令并返回单个更新计数。 DBMS 按照将它们添加到命令列表中的 Sequences 执行命令,因此它将首先添加 Amaretto 的值行,然后添加 Hazelnut 的行,然后添加 Amaretto decaf 的行,最后添加 Hazelnut decaf 的行。如果所有四个命令都成功执行,则 DBMS 将按执行 Sequences 返回每个命令的更新计数。表示每个命令影响多少行的更新计数存储在数组updateCounts
中。
如果批处理中的所有四个命令都成功执行,则updateCounts
将包含四个值,所有这些值均为 1,因为插入会影响一行。与stmt
关联的命令列表现在将为空,因为当stmt
调用方法executeBatch
时,先前添加的四个命令已发送到数据库。您可以随时使用clearBatch
方法显式清空此命令列表。
Connection.commit
方法使对COFFEES
表的更新批量永久化。由于以前已禁用此连接的自动提交 Pattern,因此需要显式调用此方法。
下一行为当前的Connection
对象启用自动提交 Pattern。
this.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);
处理批处理更新异常
调用方法executeBatch
时,如果(1)添加到该批处理中的一条 SQL 语句产生一个结果集(通常是一个查询),或者(2)批处理中的其中一条 SQL 语句未成功执行,您将得到BatchUpdateException
由于其他原因。
您不应该在一批 SQL 命令中添加查询(一个SELECT
语句),因为返回更新计数数组的executeBatch
方法期望成功执行的每个 SQL 语句都有一个更新计数。这意味着只有返回更新计数的命令(如INSERT INTO
,UPDATE
,DELETE
)或返回 0 的命令(如CREATE TABLE
,DROP TABLE
,ALTER TABLE
)才能使用executeBatch
方法成功地成批执行。
BatchUpdateException
包含一个更新计数数组,该数组与方法executeBatch
返回的数组相似。在这两种情况下,更新计数与产生它们的命令的 Sequences 相同。这将告诉您批处理中成功执行了多少个命令以及它们是哪个。例如,如果成功执行了五个命令,则数组将包含五个数字:第一个数字是第一个命令的更新计数,第二个是第二个命令的更新计数,依此类推。
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
interface插入新行。如果您try插入新行,并且 JDBC 驱动程序数据库不支持此功能,则会引发SQLFeatureNotSupportedException
异常。
以下方法CoffeesTable.insertRow
通过ResultSet
对象将一行插入COFFEES
中:
public void insertRow(String coffeeName, int supplierID,
float price, int sales, int total)
throws SQLException {
Statement stmt = null;
try {
stmt = con.createStatement(
ResultSet.TYPE_SCROLL_SENSITIVE
ResultSet.CONCUR_UPDATABLE);
ResultSet uprs = stmt.executeQuery(
"SELECT * FROM " + dbName +
".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);
} finally {
if (stmt != null) { stmt.close(); }
}
}
本示例使用两个参数ResultSet.TYPE_SCROLL_SENSITIVE
和ResultSet.CONCUR_UPDATABLE
调用Connection.createStatement
方法。第一个值使ResultSet
对象的光标向前和向后移动。如果要在ResultSet
对象中插入行,则必须使用第二个值ResultSet.CONCUR_UPDATABLE
。它指定它可以更新。
在 getter 方法中使用字符串 的相同规定也适用于 updater 方法。
方法ResultSet.moveToInsertRow
将光标移动到插入行。插入行是与可更新结果集关联的特殊行。它实际上是一个缓冲区,可以在将行插入结果集中之前通过调用 updater 方法来构造新行。例如,此方法调用方法ResultSet.updateString
将插入行的COF_NAME
列更新为Kona
。
方法ResultSet.insertRow
将插入行的内容插入到ResultSet
对象和数据库中。
注意 :用ResultSet.insertRow
插入一行后,应将光标移到插入行以外的其他行。例如,本示例使用方法ResultSet.beforeFirst
将其移动到结果集中的第一行之前。如果您的应用程序的另一部分使用相同的结果集,并且光标仍指向插入行,则可能会出现意外的结果。