|
SQLWarning 是 SQLException 的子类,但成因和其他的例外不一样。程序员必 须明确地索取警告。"连接"、"语句"和 ResultSet 全都拥有允许 检索的 getWarnings() 方法。还有避免复制检索的 clearWarnings()方法。SQLWarning 类本身仅仅添 加了方法getNextWarning() 和setNextWarning()。
SQLWarning 非常类似于传统的编译程序警告:发生不完全正 确的某些事情,但严重结果又不足以结束处理。重要性程度是否达到了需要调查 的地步,取决于操作和上下文。"可卷动的结果集合" 部分提到了 SQLWarning的范例。
"语句"在进行下一个执行时自动地清除警告。ResultSet 每次 访问新行时都清除警告。API 文档对于"连接"而言是静态的。为谨慎起见,请在 警告给出后发布 clearWarnings()。
获取 SQLWarning 的典型代码如下:
try
{
...
stmt = con.createStatement();
sqlw = con.getWarnings();
while( sqlw != null)
{
// handleSQLWarnings
sqlw = sqlw.getNextWarning();
}
con.clearWarnings();
stmt.executeUpdate( sUpdate );
sqlw = stmt.getWarnings();
while( sqlw != null)
{
// handleSQLWarnings
sqlw = sqlw.getNextWarning();
}
} // end try
catch ( SQLException SQLe)
{
...
} // end catch
DataTruncation 是 SQLWarning 有点奇特的子类。如果出现在读取时,则发布 SQLWarning;如果发生在写入/更新时,则给出 SQLException。 实际上,它仅仅涉及写入/更新操作,因此按 SQLException 的方 式进行处理,并始终处于 SQLState 为 01004 的状态。
数据截断主要表示读取或写入的信息要比请求的少。一些数据库/驱动程序会 接受超过列容量的数据,它们会将数据截断,再写入已截断数据,并通过 DataTruncation SQLException 报告"You gave me too much data, but I handled it."
DataTruncation 类包括下列与截断数据信息有关的方法: getDataSize(), getIndex(), getParameter(), getRead(), and getTransferSize()。
下面的是一组从 Cloudscape,UDB2/NT 和 DB2/400处返回的、特定地用于提 示问题的实际错误信息。详情请查看本节 练习。
DELETE FROM JJJJTee
WHERE Entry = 97
CS Result:
0 rows processed.
UDB2/NT 结果:
0 rows processed.
DB2/400 结果:
DELETE FROM JJJJTee WHERE Entry = 97
Statement Warnings:
[SQL0100] Row not found for DELETE.
SQL State: 02000
Vendor Error Code: 100
0 rows processed.
INSERT INTO JJJJTee
VALUES (25, 'Rosa', 'Petite', 'Blue')
CS Result:
INSERT INTO JJJJTee VALUES (25, 'Rosa', 'Petite', 'Blue')
problems with executeUpdate:
已放弃语句,因为它已经在单值健或主键约束中引起了重复键值。
SQL State: 23500
Vendor Error Code: 20000
UDB2/NT 结果:
INSERT INTO JJJJTee VALUES (25, 'Rosa', 'Petite', 'Blue')
problems with executeUpdate:
[IBM][CLI Driver][DB2/NT] SQL0803N One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because they would produce duplicate rows for a table with a primary key, unique constraint, or unique index. SQLSTATE=23505
SQL State: 23505
Vendor Error Code: -803
DB2/400 结果:
INSERT INTO JJJJTee VALUES (25, 'Rosa', 'Petite', 'Blue')
problems with executeUpdate:
[SQL0803] Duplicate key value specified.
SQL State: 23505
Vendor Error Code: -803
<UPDATE JJJJTee
SET TColor = 'Black'
WHERE TColor = 'Appetite'
CS Result:
0 rows processed.
UDB2/NT 结果:
0 rows processed.
DB2/400 结果:
UPDATE JJJJTee SET TColor = 'Black' WHERE TColor = 'Appetite'
Statement Warnings:
[SQL0100] Row not found for UPDATE.
SQL State: 02000
Vendor Error Code: 100
0 rows processed.
DROP TABLE IDontExist
CS Result: DROP TABLE IDontExist
problems with executeUpdate:
Table 'IDONTEXIST' does not exist.
SQL State: 42X05
Vendor Error Code: 20000
UDB2/NT 结果: DROP TABLE IDontExist
problems with executeUpdate:
[IBM][CLI Driver][DB2/NT] SQL0204N "userID.IDONTEXIST" is an undefined name. SQLSTATE=42704
SQL State: 42S02
Vendor Error Code: -204
DB2/400 结果:
DROP TABLE IDontExist
problems with executeUpdate:
[SQL0204] IDONTEXIST in JGURU type *FILE not found.
SQL State: 42704
Vendor Error Code: -204
UPDATE JJJJTee
SET TSize = 'Small Doppelganger'
WHERE TSize = 'Small'
CS Result:
UPDATE JJJJTee SET TSize = 'Small Doppelganger' WHERE TSize = 'Small'
problems with executeUpdate:
Non-blank characters were found while truncating string 'Small Doppelganger' from length 22 to length 10.
SQL State: 22001
Vendor Error Code: 20000
UDB2/NT 结果:
UPDATE JJJJTee SET TSize = 'Small Doppelganger' WHERE TSize = 'Small'
problems with executeUpdate:
[IBM][CLI Driver][DB2/NT] SQL0433N Value "Small Doppelganger" is too long. SQLSTATE=22001
SQL State: 22001
Vendor Error Code: -433
DB2/400 结果:
UPDATE JJJJTee SET TSize = 'Small Doppelganger' WHERE TSize = 'Small'
problems with executeUpdate:
[SQL0404] Value for column or variable TSIZE too long.
SQL State: 22001
Vendor Error Code: -404
UPDATE JJJJTee
SET TSize = 'Small '
WHERE TSize = 'Small'
CS Result:
3 rows processed.
UDB2/NT 结果:
3 rows processed.
DB2/400 结果:
3 rows processed.
DROP TSBLE BadSQL
CS Result:
DROP TSBLE BadSQL
problems with executeUpdate:
Syntax error: Encountered "TSBLE" at line 1, column 6.
SQL State: 42X01
Vendor Error Code: 20000
UDB2/NT 结果:
DROP TSBLE BadSQL
problems with executeUpdate:
[IBM][CLI Driver][DB2/NT] SQL0104N An unexpected token "TSBLE" was found following "DROP ". Expected tokens may include: "JOIN <joined_table>". SQLSTATE=42601
SQL State: 42601
Vendor Error Code: -104
DB2/400 结果:
DROP TSBLE BadSQL
problems with executeUpdate:
[SQL0104] Token TSBLE was not valid. Valid tokens: DISTINCT DATA.
SQL State: 42601
Vendor Error Code: -104
练习
元数据是关于数据的数据(或信息)。JDBC 允许程序员通过元数据类去发现 关于数据库和任何给定 ResultSet 的大量信息。
为了发现数据库的信息,必须获取 DatabaseMetaData 对象。一旦程序已经获取有效的"连接",该代码就会获取元数据对象:
DatabaseMetaData dbmd = con.getMetaData();
好在只调用所希望信息的方法,就可以做到这一点。大部分的问题是:
DatabaseMetaData类中有大约 150 种方法。无疑,精通(乃至 了解)可用信息本身就是一个很重的任务。不过,初步了解一下 API 可能有所帮 助。
许多方法返回 ResultSet,对此程序员必须逐步处理以获取特 定的信息。
部分方法,包括返回数据库和表组件的信息,使用的名称格式十分混乱。根据 数据库的不同,信息可能采取大写字母、小写字母或大小写混合的形式,格式是 区分大小写的。结果,必须调用方法在试图获取信息前,找出信息存储的方式。
虽然这可能有些不令人满意,但可以轻松获得最常用的 DatabaseMetaData 信息,如数据库名称、驱动程序名称、版本、可用的最大连接数、 SQL一致性等 等。很多程序根本不需要此类信息。注意,给定的 DBMS 可能不为所有方法提供 信息,检查会返回零值或空字符串的对象。
对本课程中的程序已提供了链接,这些程序使用下面"练习"部分的 DatabaseMetaData。 转到每个练习的程序"解决方案"部分,并在 DatabaseMetaData 中 搜索用法范例。
练习
为了发现给定 ResultSet 的信息,必须获取 ResultSetMetaData 对象。一旦程序已经获取有效的 ResultSet ,该代码就会获取元 数据对象:
ResultSetMetaData rsmd = rs.getMetaData();
ResultSetMetaData 比 DatabaseMetaData 更易 于管理,约有 25 种方法。使用 ResultSetMetaData 后,应用程 序可以找到所返回列的数、各列建议的显示大小、列名、列类型等等。注意,给 定的 DBMS 可能不为所有方法提供信息,所以检查会返回零值或空字符串的对象。
对本课程中的程序已提供了链接,这些程序使用下面"练习"部分的 ResultSetMetaData。 转到每个练习的程序"解决方案"部分,并在 ResultSetMetaData 中搜索用法范例。
练习
大多数的数据库提供标量函数(有时称为内部函数),用于执行列的特定值上 的操作,甚至提供迅速创建列的值。JDBC 规范支持不同的数学值、字符串、系统、 时间和日期、X/Open Call Level Interface(打开调用级别界面,CLL)指定的 类型转换功能;如果底层的 DBMS 支持某功能,则 JDBC Compliant 驱动程序也 必须支持。这些功能的名称应该和 X/Open 名称匹配,虽然情况不总是这样。对 其功能或从应用程序切换到数据库而言,标量函数非常重要。
JDBC 提供这些方法,以确定标量函数:getNumericFunctions()、 getStringFunctions()、getSystemFunctions()、 getTimeDateFunctions()、和supportsConvert() 的两个版本。getXXXFunctions() 以逗号分开的字符串返回函数名。
因为不同的数据库对标量函数调用使用不同的语法,所以 JDBC 定义了专门的 扩展符语法。JDBC驱动程序会理解这些语法,并映射到适当的语法以找出基础数 据库。扩展符也用于 LIKE 字符、日期与时间文字、存储的步骤调 用和外部连接。标量函数的扩展符是 fn。实际的函数名和自变数 都放在花括号内,如{ fn }。
在 SQL 语句中,标量函数通常和列一起使用。例如, PI() 数 值函数就是如此:
UPDATE myTable
SET circularVal = squared * { fn PI() }
...
or
SELECT { fn concat( string, "bean" ) }
...
受到支持的功能,请查阅 DBMS 手册。
练习
存储过程是用户生成的函数或步骤,在注册数据库后,可以称为客户应用程 序。这些过程很有价值,因为它们可以将工作切换到服务器,并减少编码,对于 复杂的操作尤其如此。令人遗憾的是,对于创建存储过程而言,没有标准的方式、 要求甚至语言。而且并非全部的数据库都支持它们。在这种情况下,就没有办法 创建常用的练习,所以本节只限于讨论使用 JDBC 标准方法调用存储过程及其代 码片断。当然,创建存储过程可以一次性操作成功,并提示其名称和要求的参数 类型。
有几种 DatabaseMetaData 方法可以返回特定数据给存储过程 提供的支持的信息。
supportsStoredProcedures() 确定 DBMS 是否支持 JDBC 标准 的存储过程扩展符语法。
getProcedures() 返回可用存储过程的列表,同时 getProcedureColumns() 描述参数和结果。
getProcedureTerm() 会将供应商对存储过程的建议名称通知给 程序员。
在调用时,如同标准方法或功能一样,存储过程可以接收零、更多自变数或参 数,也称为 IN 参数。它们可以返回 ResultSet、更新计数、结果 参数、和/或零或更多 OUT 参数。另外,过程过程可以拥有 INOUT 参数,而在这 样情况下会向它发送值,并以相同的变量返回不同的值。IN、OUT 和 INOUT 参数 都封装在括号表达式中,仅仅以数字作为区别。该数字相当于参数标志出现的顺 序(?---问号),从 1 而不是 0 开始。
"扩展符语法和标量函数"中曾经提到,存储过程要 求 JDBC 扩展符语法以进行标准调用。驱动程序会再次处理实际映射。基本格式 的组成是: call sp_name 或 ? = call sp_name 加上可选参数。全部都以花括号括起来。 下面是几个范例形式,下一段将更详细 地进行介绍。
A - 不带参数,它返回 ResultSet 或行数。
{ call sp_A }
B - 单一参数,返回结果参数。假设为 int 结果参数和 String IN 参数:
{ ? = call sp_B( ? ) }
C - 多参数,它返回 ResultSet 或行数。假设为 int IN、 OUT 和 INOUT 参数:
要向数据库发送实际的存储过程执行请求,可使用 CallableStatement, 它扩展了 PreparedStatement。在创建 CallableStatement 时,可以引用或作为字符串变量使用上面讨论的扩展符语法。应该务必小心,保 证语法的正确:该语句发送字符串。
A -
CallableStatement cstmt =
con.prepareCall( "{ call sp_A }" );
B -
CallableStatement cstmt =
con.prepareCall( "{ ? = call sp_B( ? ) }" );
C -
CallableStatement cstmt =
con.prepareCall( "{ call sp_C( ? ? ? ) }" );
在调用存储过程前,参数标志必须与变量和类型匹配。类型信息,请查看 "Java - SQL 类型等效性"。
IN 参数用从 PreparedStatement 继承下来 的 setXXX() 设置。
必须使用 CallableStatement.registerOutParameter() 方法 之一注册 OUT 参数。
必须设置和注册 INOUT 参数。
实际的调用将根据预期结果照例采用 executeQuery()、 executeUpdate() 或 execute()。
A -
CallableStatement cstmt =
con.prepareCall( "{ call sp_A }" );
就无返回值而言:
cstmt.execute(); // could use executeUpdate()
就已返回的 ResultSet 而言:
ResultSet rs = cstmt.executeQuery();
就已返回的更新计数而言:
int iUC = cstmt.executeUpdate();
B -
CallableStatement cstmt =
con.prepareCall( "{ ? = call sp_B( ? ) }" );
// int result parameter
cstmt.registerOutParameter( 1, Types.INTEGER );
// String IN parameter
cstmt.setString( 2, "M-O-O-N" );
cstmt.execute(); // could use executeUpdate()
int iRP = cstmt.getInt( 1 );
C -
CallableStatement cstmt =
con.prepareCall( "{ call sp_C( ? ? ? ) }" );
设置:
// set int IN parameter
cstmt.setInt( 1, 333 );
// register int OUT parameter
cstmt.registerOutParameter( 2, Types.INTEGER );
// set int INOUT parameter
cstmt.setInt( 3, 666 );
// register int INOUT parameter
cstmt.registerOutParameter( 3, Types.INTEGER );
就无返回而言 ( OUT 和 INOUT 除了:)
cstmt.execute(); // could use executeUpdate()
// get int OUT and INOUT
int iOUT = cstmt.getInt( 2 );
int iINOUT = cstmt.getInt( 3 );
就已返回的 ResultSet而言:
ResultSet rs = cstmt.executeQuery();
// get int OUT and INOUT
int iOUT = cstmt.getInt( 2 );
int iINOUT = cstmt.getInt( 3 );
就已返回的更新计数而言:
int iUC = cstmt.executeUpdate();
// get int OUT and INOUT
int iOUT = cstmt.getInt( 2 );
int iINOUT = cstmt.getInt( 3 );
所有这些都是很细致的工作,但格式应该明确。
在SQL术语中,事务是逻辑工作单元(logical unit of work,LUW)构成的一 个或多个语句。这在某种含义上意味着,一切都是事务。不过,通常而言,术语 事务用来表示或全或无的系列操作;也就是说,要么一切十分成功,要么什么也 没有发生。
典型的事务是从银行帐户提款,并存放到另一个。只要提款完成,金额就消失 了。另一个范例是复式簿记记帐法中的借方和贷方:借方和贷方都必须完成。第 三个方面,即本节练习中的内容,是保证 INSERT、UPDATE、 或 DELETE 的无错误操作。
虽然一些 SQL非标准语言有专门的开始和结束事务语句,但总的来说事务会从 程序开始持续进行到语句联锁。并从该点,开始新的事务。这是 JDBC 所使用的 模型。JDBC 驱动程序的默认值是 autocommit,表示每个 SQL 语句的 结果一旦执行就永久保留。这是本课程到现在为止无需考虑事务的原因,而且在 很多情况下都很能让人接受。
注意: 在 autocommit 模式中,联锁出现于"语句"完成时。"语句" 返回 ResultSet 时,直到最后一行已经检索或 ResultSet 关闭,"语句"才完成。
"连接"的 setAutoCommit(布尔型的autoCommit)方法是处理事务的关键。每 个语句采用Connection.setAutoCommit(true)联锁;采用 Connection.setAutoCommit(false)进行编程事务控制。可以随意 调用该方法,必要时,可在程序多次调用。调用Connection.setAutoCommit(false) 后,Connection.commit()和Connection.rollback() 就用来控制LUWs(应该是LUsW,正如indexes应该是indices 一样,但应该具体情况具体分析)。
一旦 autocommit 设置为 false,全部数据库 DML 语句在联锁前都可以视为 暂时性语句。JDBC 支持带 Connection.commit() 方法的联锁。最后一次联锁到数据库后,上述基本上是全部永久安置所必须的一 切内容,虽然计时偶而会出现错误。以前,很多的数据库设置了锁,甚至对读取 操作也是如此,以防止其他用户访问同一数据。自动的独占性读取锁定在时间的 这一点上相当稀少,但是从那时开始到现在仍然适用的好口号是"早联锁,常联锁"。 当然,不能太早。
注意: 事务中的 DDL 语句可以忽视或形成联锁。该特性是由 DBMS 决定的, 并可以通过 DatabaseMetaData.dataDefinitionCausesTransactionCommit() 和 DatabaseMetaData.dataDefinitionIgnoredInTransactions() 显示出来。 避免意外结果的一种方法是区分 DML 和 DDL 事务。
Connection.rollback() 用来删除先前联锁或回退后已执行的操作。在发生异常或程序检测出错误状态或 数据错误时,请使用该方法。
大多数的 DBMS 允许多个用户同时对数据进行操作。有时,开发者不太关心数 据库并行问题。(在数据开始消失或数据库数据库发生异常时,开发者常常会处 于冒险状态,至少也会同主管争执。) 并行的级别和类型对性能也有影响。
JDBC 识别下列控制并行性的事务 隔离级别。
TRANSACTION_NONE
TRANSACTION_READ_COMMITTED
TRANSACTION_READ_UNCOMMITTED
TRANSACTION_REPEATABLE_READ
TRANSACTION_SERIALIZABLE
使用 Connection getTransactionIsolation() 和 setTransactionIsolation( int 级别)方法确定并设置期望的隔离 级别。JDBC 驱动程序具备默认值的隔离级别,常常是基础数据库的默认值。并非 全部的数据库都支持上述所有设置。
在数据库操作中适当而有效的并行处理极端重要,很多应用程序根本无法达到 要求。令人遗憾的是,全面讨论几乎要占很大的篇幅,所以请参看 DBMS 供应商 信息和"资源"以了解更多信息。
下面是一个典型事务处理代码的范例:
con.setAutoCommit( false );
...
bError = false;
try
{
for( ... )
{
// validate data, set bError true if error
if( bError )
{
break;
}
stmt.executeUpdate( ... );
}
if( bError )
{
con.rollback();
}
else
{
con.commit();
}
} // end try
catch ( SQLException SQLe)
{
con.rollback();
...
} // end catch
catch ( Exception e)
{
con.rollback();
...
} // end catch
练习
"批更新装置"是 JDBC 2.0 的新功能,允许以多个语句为单位向数据库进行发 送,从而提高性能。注意,实现该功能不需要驱动程序,采用了驱动程序并不比 常规提交更有效率。即使如此,使用批更新无需额外努力(报告除外),且潜在 的好处可能很有价值。驱动程序的支持可以使用 DatabaseMetaData.supportsBatchUpdates() 方法确定。
JDBC 2.0 语句是使用命令的自动关联列表创建的。addBatch()、 clearBatch() 和 executeBatch() 方法用于操作和 执行列表。executeBatch()) 则返回 int 阵列,该 阵列为每个已执行的 SQL 语句提供完成或错误信息。JDBC 的建议是在批更新"供 适当的错误处理"时将 autocommit 设置为 false。这样做也可以获得全部的事务 处理好处。
可以在更新计数阵列中返回的 int 值是:
-3--操作错误。驱动程序的拥有可以在第一个错误处停止 的选项,会给出 BatchUpdateException 或报告错误并继续执行。 仅在出现后一种情况时会显示该值。
-2--操作成功,但是作用过的行数未知。
零--DDL 语句或没有受操作影响的行。
大于零--DDL 语句成功,受操作影响的行数。
下面是一个典型批更新的范例:
try
{
con.setAutoCommit( false );
...
bError = false;
stmt.clearBatch();
// add SQL statements
stmt.addBatch( sUpdate1 );
stmt.addBatch( sUpdate2 );
stmt.addBatch( sUpdate3 );
// execute the statements
aiupdateCounts = stmt.executeBatch();
} // end try
// catch blocks
...
finally
{
// determine operation result
for (int i = 0; i < aiupdateCounts.length; i++)
{
iProcessed = aiupdateCounts[i];
if( iProcessed > 0 ||
iProcessed == -2
)
{
// statement was successful
...
}
else
{
// error on statement
bError = true;
break;
}
} // end for
if( bError )
{
con.rollback();
}
else
{
con.commit();
}
} // end finally
Statement.executeBatch() 可以给出 BatchUpdateException, 它是 SQLException 的子类。它唯一的额外方法是 getUpdateCounts(), 该方法允许程序员获取供报告用的更新计数阵列。您可能已经注意到了,批处理 在捕捉例外上还有问题。BatchUpdateException 的一个奇怪之处 是,它没有给其他的 BatchUpdateException 提供链接方法,只继 承 SQLException.getNextException()。使用"批更新装置"的代码 也可以捕捉和处理 SQLException。
下面是处理批更新例外的范例:
catch( BatchUpdateException bue )
{
bError = true;
aiupdateCounts = bue.getUpdateCounts();
SQLException SQLe = bue;
while( SQLe != null)
{
// do exception stuff
SQLe = SQLe.getNextException();
}
} // end BatchUpdateException catch
catch( SQLException SQLe )
{
...
} // end SQLException catch
练习注释: UDB2/NT从 DatabaseMetaData.supportsBatchUpdates() 处返回 false。以下练习已经在 Cloudscape 和 DB2/400 上进行了试验。
练习
到目前为止,所有的 ResultSet 都已经以连续的方式进行了运 用,从头至尾使用 ResultSet.next() 获取各行。如在语句、 ResultSet 和与数据库互动中介绍的,在本课程自始至终也可以看 到, ResultSet 是通过语句获取的,通常使用 executeQuery 方法。到现在为止,语句的创建都是使用
stmt = con.createStatement();
该方法是 JDBC 1.0 中唯一可用的方法。在 JDBC 2.0 中,存在着允许创建可卷 动和/或可更新的 ResultSet 新方法。
createStatement(
int resultSetType,
int resultSetConcurrency )
resultSetType 可以是
ResultSet.TYPE_FORWARD_ONLY--这是默认值,和 JDBC 1.0 中的一样:仅转 交移动,列一般地仅能读取一次。在 ResultSet.next() 返回 false 时, ResultSet 数据就不再有效并自动关 闭。
ResultSet.TYPE_SCROLL_INSENSITIVE 允许创建 ResultSet, 其中的光标可以向后、向前和随机移动。这是静态数据:数据库中对当前 ResultSet 中选定的行进行的任何更改都是不可见的。也就是说, ResultSet 对数据修改不敏感。
ResultSet.TYPE_SCROLL_SENSITIVE 允许创建 ResultSet,其 中的光标可以向后、向前和随机移动。这提供了数据的动态视图:数据库中对当 前 ResultSet 中选定的行进行的任何更改都是可见的。也就是说, ResultSet 对数据修改很敏感。
resultSetConcurrency 可以是
ResultSet.CONCUR_READ_ONLY - 这是默认值,和 JDBC 1.0中的一样:
ResultSet.CONCUR_UPDATABLE 允许通过新的 ResultSet 方法 和定位性能对编程数据进行更改。
可更新的 ResultSet 优点、缺点都有,本课程不作深入讨论。 更多的信息,请查看 Java 教程或 JDBC 教程和 参考资料(第二版)联机版本中高级教程的第 3.3 部分。
注意,已请求的 ResultSet 类型,即使受到驱动程序的支持, 也可能无法返回。如果出现该情况,驱动程序会在连接时发出 SQLWarning。 另外, DatabaseMetaData.supportsResultSetType() 可用于确定 驱动程序支持的 ResultSet类型, ResultSet.getType() 提供实际返回的 ResultSet 的类型。详情请查看请 求未支持的特性。
可卷动的 ResultSet 的获取方式如任何其他的一样,通常都是 通过 Statement.executeQuery() 进行。不过,有了 ResultSet, 就可以使用以下方法:
absolute()
afterLast()
beforeFirst()
first()
getRow()
isAfterLast()
isBeforeFirst()
isFirst()
isLast()
last()
moveToCurrentRow()--仅对可更新的 ResultSet 有效。
moveToInsertRow()--仅对可更新的 ResultSet 有效。
previous()
relative()
可卷动 ResultSets 的驱动程序性能和但实现级别会变化,有 时很显著。请检查文档。下面是在使用可卷动的 ResultSets 时应 考虑的其他几个因素,但决不是全部。
可卷动的 ResultSet,正如不可卷动的 ResultSet 一样,检索时定位在第一行前。
所有的行检索时,语句才视为完成。这在 ResultSet.next() 检索最后一行时发生。一些驱动程序在 autocommit 开启时将此视为联锁该语句。 结果, ResultSet 会关闭,并在下一个访问尝试时给出 SQLException。 为了可移植性,请将 autocommit 设置为 false。
ResultSet.getRow() 会在某些(甚至全部)位置返回零。这尤 其意味着,使用已赋值的 ResultSet.last()、 ResultSet.getRow() 序列在数据库中(甚至在同一数据库的驱动程序中)获取的行数不太可靠。
ResultSet.absolute() 在传递零时会给出 SQLException。
但 ResultSet.relative() 不会更改光标的位置。不过,至少一 个供应商会不检查零值而从 ResultSet.absolute() 中调用 ResultSet.relative()。请考虑潜在的(笔者有此教训)结果。
练习
JDBC 2.0包括处理若干 SQL3 数据类型的类。本节介绍 LOB 或大对 象。LOB 的定义有两种类型:BLOB---二进制大对象和 CLOB ---字符大对象。
从经典关系数据库理论的来看,Clob 属于临界类型---字符很多--- Blob 实 际上根本不是一种类型。全部所知就是,Blob包含一些可以是任何内容的字节数。 显然,这会损害数据独立性的概念。在有其他非常易于接受的方法,使用数据库 跟踪基本上是图形、声频或其他的类型的二进制文件时,尤其如此。注意,没有 机制可以阻止向视为图像的 Blob 中写入声音文件。也没有机制可以去了解原始 源的名称或任何类似的考虑。
SQL 定位器类型在概念上类似于指针或记录实体的其他信息。JDBC 开发者不 必处理定位器,但有助于理解概念。因为定位器实际上是 JDBC 驱动程序在阵列、 Blob 或 Clob 列中预期会找到的内容。也就是说, 实际数据不会在 ResultSet 转到下页,只是转到定位器。可以明 确地按需要请求待返回的 LOB 数据,这被称作具体化数据。无疑,这比将每列大 量未知的字节进行转下页处理要有效的多。实际数据被 DBMS 存储到"其它地方" 并从该处检索。
Clob 是 SQL CLOB 的 JDBC 界面映射。Clob 通过 ResultSet 或 CallableStatement 的 getClob() 方法获取。 Clob 具有各种方法,以获取数据的子字符串、数据的长度、另一 个 Clob 的位置或当前的 Clob 中的字符串。这些 方法的运行不需要具体化数据。要具体化数据,可使用 getAsciiStream() 或 getCharacterStream()(就Unicode流而言),然后从返回的流 中建造可用对象。
对于 Clob 存储,请使用 PreparedStatement 中的 setClob(),或可更新 ResultSet 中的 updateObject()。讨论到此为止,从根本上而言,范例会检索一行 中的 Clob,并将其放到同一或不同表中的另一行中。
但是首先如何填充 Clob 呢?Clob getAsciiStream() 和 getCharacterStream() 方法 可以解决这一问题:使用 PreparedStatement 的 setAsciiStream() 或 setCharacterStream() 方法填充Clob。
Blob 是 SQL BLOB 的 JDBC 界面映射。Blob 通过 ResultSet 或 CallableStatement 的 getBlob() 方法获取。 Blob 具有获取字节数、确定另一个 Blob 起动位置 或当前 Blob 字节阵列的方法,这些方法的运行不需要具体化数 据。要具体化数据,可使用 getBinaryStream()或 getBytes() (就部分或全部 Blob 而言),然后从返回的流或字节中建造可用 对象。
对于 Blob 存储,请使用 PreparedStatement中 的 setBlob(),或可更新 ResultSet 中的 updateObject。其讨论也到此为止,从根本上而言,范例会检索一 行中的 Blob,并将其放到同一或不同表中的另一行中。
首先,Blob 数据如何起作用呢?请再次检查 Blob 方法,这次使用 getBinaryStream() 和 getBytes(): 使用 PreparedStatement 的 setBinaryStream() 或 setBytes() 方法填充 Blob。

本课程使用的 Cloudscape 版本不支持 SQL3 数据类型。以下练习已对 UDB2/NT 和 DB2/400 进行了测试。
练习
- 在 Blob 中存储图像
- 从 Blob 中检索并显示图像
JDBC Compliant 驱动程序的基本要求是,必须支持 ANSI SQL- 92 Entry Level,它基本上是SQL-89 的第2级。以下是不详尽的、基本的 SELECT、 INSERT、 UPDATE 和 DELETE 语句以 外的 SQL-92 Entry Level 功能的列表。
from 从句中的多个表。
数据类型:characterType, decimalType, integerType, smallintType, floatType, realType, doublePrecisionType, 和 numericType.
简单 SQL 表达式:and, or, not, like, =, <>, 算术函数, joins, group bys, having, order by clauses, 和汇总函数 (如sum、count、max、min)
简单的表和列描述符:tableName, columnName.
表描述符中的唯一和主键约束。
检查列描述符中的约束。
支持相关的子查询和 EXIST 子查询。
充分支持功能中的 Distinct。
支持并集。
更多完整数据,包括 SQL- 92 的中级和完全级别详情,请参看: FIPS PUB 127-2: 数据库语言 SQL 的标准。
DatabaseMetaData 方法 supportsANSI92EntryLevelSQL()、 supportsANSI92IntermediateSQL() 和 supportsANSI92FullSQL() 用于在运行时发现 SQL 的一致性级别,以及特定数据库和驱动程序的性能。 JDBC Compliant 驱动程序必须对 supportsANSI92EntryLevelSQL() 返回 true。
另外,ODBC 定义的 SQL 语法支持级别可以通过 DatabaseMetaData 方法 supportsMinimumSQLGrammar()、supportsCoreSQLGrammar() 和 supportsExtendedSQLGrammar() 确定下来。JDBC Compliant 驱动程序必须对 supportsMinimumSQLGrammar() 返回 true。您可 能想知道, ODBC 是不是纯粹的 Microsoft 标准。在 AcuODBC SQL 一致性中可以找到显示 SQL 语法级别的表。 ODBC 3.51 版本中可以找到更多的 ODBC 信息。
根据应用程序的不同,可能会返回不支持某些功能的消息,或使用不同的算法 根据 SQL 级别或驱动程序支持的语法类型及其基础 DBMS 提供该功能。
在 JDBC 2.0 中,实际上有两个包。本课程介绍核心 API 及其功能。第二个 包,被称为 JDBC 2.0 可选包( javax.sql ),包括 DataSource 界面、连接 池、分布式事务及其行集合。Maydene Fisher 编著的文章 JDBC 2.0 可选包概述了额外的功能。
JDBC 和 JDBC 2.0可选包是 Java 2 平台企业版本(J2EE)的组成部分,它包括创建企业-类服务器-端应 用程序所使用的许多技术。
本课程的本节简短地介绍 JavaServer 页基本原理中提到的 JDBC 和 JSPTM 结合使用的各个方面。
本课程集中介绍核心 JDBC 2.0 API。不过,连接池不仅令人满意,也是 JSP 实用的必需技术,并有助于介绍 JDBC 2.0 可选包的一些特性。可以从 JDBC 下载页面获得 JDBC 2.0 可选包二进制文件。DataSource、 ConnectionPoolDataSource 和 PooledConnection 的更多信 息,请查看 Maydene Fisher 编著的 JDBC 2.0 Optional Package 的前 3 部分。
如文中介绍,使用 DataSource 和 PooledConnection 的程序主干代码是:
import javax.naming.*;
import javax.sql.*;
...
Context context = new InitialContext();
DataSource ds = (DataSource)ctx.lookup(
"jdbc/DataSource" );
Connection con = ds.getConnection(
"userID", "password" );
...
finally
{
if( con != null ) { con.close(); }
}
记住,javax.naming 包属于 Java 命名和目录界面TM ( JNDI ),也是 JDK 1.3 的内容。此外,请记住连接池类会越过 close() 方法,并将连接标记为可用。这就是确保 close() 被调用的重要 性。否则,池将连接视为"使用中",并在第二次请求时创建新的连接,这就损失 了池的好处。
上述的代码可以这么直接了当,是因为预计可以由数据库管理员使用 DBMS 供 应商提供的工具来创建 DataSource (无论是否经过池处理)。令 人遗憾的是,这使得我们几乎不可能给出常规的练习,因为每个供应商可以提供 不同的任务完成方法。使用 JNDI 设置 DataSource 的介绍,请查 看 高级教程 3.7 部分的 3.7.3 节。
上述应用程序代码的 JSP 版本非常类似:
<%@ page import="javax.naming.*, javax.sql.*" %>
...
<%
Context context = new InitialContext();
DataSource ds = (DataSource)ctx.lookup(
"jdbc/DataSource" );
Connection con = ds.getConnection(
"userID", "password" );
...
finally
{
if( con != null ) { con.close(); }
}
%>
在显示信息的应用程序中,应该创建粒媒,该粒媒可以处理查询、于请求时可 以返回包含行里的列和/或以单一字符串按照需要报告列的 QueryRows 或类似对象。粒媒可以根据控制的优先级和功能的分类将 DataSource 或者 Connection 作为自变数处理。.
如果有分页显示,就可以使用可卷动的结果集练习中采用的相对方法,或获取 检索的行数,然后使用绝对行和页面位置。JSP 卷动的更多想法和程序包,请查 看页面调度程序标记库。实现 JSP 的更多想法和信息,请参考 JavaServer 页面 基本原理短训班和资源。
JavaTM 2 SDK 企业版中的 Cloudscape 数 据库在大多数 JDBCTM 2.0基本原理课程都得 到了应用。本文介绍的版本是 Cloudscape 3.0.4。第一步是获取平台的 J2EETM SDK 和文档,这些可以从 J2EE 主页的下载 & 规范部分找到。请务必阅读并遵行 安装说明书。
如果希望在线阅读文档,请查看 J2EE 文档。Cloudscape 的在线文档,请查看 Cloudscape 3.0.1 文档。
如果下载 J2EE 的唯一目的是将 Cloudscape 用于学习本课程,就不必按安装 说明书中的建议更改 userconfig 脚本。务必执行下列操作步骤:
设置环境变量 J2EE_HOME。在 NT 上,这是 J2EE SDK 的驱动 器字母和父目录,例如:
set J2EE_HOME=D:\j2ee
设置环境变量 JAVA_HOME。在 NT上,是 J2SE 的驱动器字母和 父目录,例如:
set JAVA_HOME=D:\j2se
请使用 J2EE 和 J2SE 目录自己的位置和名称。由此开始, J2EE_HOME 和 JAVA_HOME 都引用这些目录。
要避免类途径问题,请将下列 jars 从J2EE_HOME/lib/cloudscape 复制到并重命名为 JAVA_HOME/jre/lib/ext:
cloudscape.jar
client.jar rename to cs_client.jar
RmiJdbc.jar rename to cs_RmiJdbc.jar
tools.jar rename to cs_tools.jar
不必重命名 jars (如果愿意可以使用不同的名称),但是这样做将避免任何 因为所用的常见姓名导致的冲突。可以随意进行各种设置,但是上述步骤不会更 改适当的客户机和服务器操作。注意,该设置会绕过 J2EE,并完全允许使用 Cloudscape。如果想结合本课程的练习使用 J2EE 部分,就必须修改 jGuru 所用的数据库名称或编辑练习程序和资源包,以使用默认的 CloudscapeDB 数据库。
Cloudscape 的更多信息,请查看 J2EE_HOME/doc/cloudscape/index.html 里 Cloudscape DBMS 的下载文档。不是所有文档都适用于 J2EE 下载中包 括的特别版本。因为本课程集中介绍标准,所以不详细讨论数据库细节,并且每 个数据库的设置都不同。这类信息和启动/关闭数据库的详情,请参考自己所用数 据库和驱动程序的文档。不过,会简要地介绍启动和关闭 Cloudscape。
确保已经安装 Cloudscape 并按 Cloudscape 安装和 设置中的介绍进行了设置。
必须使用命令行启动 Cloudscape。实际命令(请务必按照需要适当地替换 J2EE_HOME和斜杠或反斜杠)是:
J2EE_HOME/bin/cloudscape -start
对于正确地关闭 Cloudscape 的数据库完整性来说,尤其重要。除非绝对不可 避免,不要使用 CTRL-C 或其他的终止进程/终止方法。也必须从命令行关闭 Cloudscape。通常,这意味着开启另一个窗/外框,并键入下列命令(同样,请按 照需要适当替换 J2EE_HOME 和斜杠或反斜杠):
J2EE_HOME/bin/cloudscape -stop
Cloudscape 在成功启动和关闭时的输出信息,请查看 http://J2EE_HOME/doc/guides/ejb/html/Tools4.html#11919。
本节是 SQL 的复习课程,与练习一起使用。它不 SQL 的百科全书式的全部资 源。只介绍 CRUD 操作所需的基本命令。
C--Create
R--Read
U--Update
D--Delete
创建表时,请使用 CREATE TABLE 语句。因为创建表操作非常 重要,所以需要最低的一致性。不过,有些数据来源,如 Text ODBC,仅仅支持 最简单的列元素,几乎没有约束支持。
CREATE TABLE <table name>
(<column element> [, <column element>]...)
列元素的形式是:
<column name> <data type>
[DEFAULT <expression>]
[<column constraint> [, <column constraint>]...]
列约束的形式是:
NOT NULL |
UNIQUE |
PRIMARY KEY
范例:
CREATE TABLE java (
version_name varchar (30),
major_version int,
minor_version int,
release_date date);
撤消表时,请使用 DROP TABLE 语句。如同 CREATE TABLE, 它也需要最低的一致性。
DROP TABLE <table name>
检索一组列时,请使用 SELECT 语句。该设置可能来自一或多 个表,可以指定标准以确定检索的行。在最低的一致性条件下,可以使用大部分 的子句。使用核心语法可以使用其他的功能。
SELECT [ALL | DISTINCT] <select list>
FROM <table reference list>
WHERE <search condition list>
[ORDER BY <column designator> [ASC | DESC]
[, <column designator> [ASC | DESC]]...]
T选择列表常常包含由逗号分隔的列的列表或全部选定的"*"。
SELECT version_name, release_date from java;
如果驱动程序支持核心一致性,也可以使用 SELECT 的 GROUP BY、HAVING 和 UNION 子句。
要执行联合运算以获取多个表的结果,WHERE 子句就必须提供 标准。如果多个表使用相同的列名,就可以用表名接上句号放在列名的前面。
SELECT employee_id, employee_name,
department_table.department_id, department_name
FROM employee_table, department_table
WHERE employee_table.department_id =
department_table.department_id;
也可以指定 from 子句中的用于表的别名,以免出现累赘的名称或少些打字 操作。
SELECT employee_id, employee_name,
d.department_id, department_name
FROM employee_table e, department_table d
WHERE e.department_id =
d.department_id;
插入行请使用 INSERT 语句。它也可以根据所支持的一致性级 别提供不同的性能。
INSERT INTO <table name>
[(<column name> [, <column name>]...)]
VALUES (<expression> [, <expression>]...)
INSERT INTO java VALUES
('2.0Beta', 2, 0, 'Aug-1-1997');
如果支持核心语法,就可以使用 SELECT 子句一次加载多行。
更新行请使用 UPDATE 语句。它需要最低的语法。
UPDATE <table name>
SET <column name = {<expression> | NULL}
[, <column name = {<expression> | NULL}]...
WHERE <search condition>
删除行请使用 DELETE 语句。它需要最低的语法。
DELETE FROM <table name>
WHERE <search condition>
在哪里可 以找到 JDBC 驱动程序的综合目录,包括它们支持的数据库?
在哪里可 以找到 SQLException.getSQLState() 可能返回的 SQLStates 列表?
在哪里可 以找到数据库 xyz 的在线文档?
如何设计 servlet/JSP,才能让查询结果像搜索引擎的结果一样在若干页面上显示?
Sun Microsystems, JDBC 主页jGuru's JDBC FAQ
Java 教程 - JDBCTM 数据库访 问
Java 开发者连接数据库访问文章
JDBC 2.0 高级教程
JDBC 性能提示
jGuru's JSP FAQ
jGuru's Servlets FAQ
Sun Microsystems 处的 Java 技术网站 包括产品和 API。
Sun Microsystems,JDBC API文档
J2EE 文档页面
JDBC API 教程和参考资料(第二版) 作者Maydene Fisher, Dr. Rick Cattell, Graham Hamilton, Seth White and Mark Hapner (Addison Wesley ISBN 0201433281) 。
用 JDBC 和 Java 进行数据库编程(第二版) 作者 George Reese (O'Reilly & Associates ISBN 1565926161) [ sample chapte] 。
FIPS PUB 127-2: 数据库语言 SQL 的标准。
结构化查询语言入门/p>
DB2 主页
Oracle 主页
SQL 完全参考 作者James R. Groff and Paul N. Weinberg (McGraw-Hill ISBN 0072118458)
Joe Celko 的智者宝典 高级 SQL 编程(第二版)作者Joe Celko (Morgan Kaufmann ISBN 1558605762) |