五、数据库编程
5.1 JDBC 的设计
-
JDBC 驱动程序类型
- JDBC 规范将驱动程序
- 驱动程序将JDBC 翻译成ODBC, 然后使用一个ODBC 驱动程序与数据库进行通信。较早版本的Java 包含了一个这样的驱动程序: JDBC/ODBC 桥,不过在使用这个桥接器之前需要对ODBC 进行相应的部署和正确的设置。在JDBC 面世之初, 桥接器可以方便地用于测试,却不太适用于产品的开发。Java 8 已经不再提供 JDBC/ODBC 桥了。
- 驱动程序是由部分Java 程序和部分本地代码组成的,用千与数据库的客户端API 进行通信。在使用这种驱动程序之前,客户端不仅需要安装Java 类库,还需要安装一些与平台相关的代码。
- 驱动程序是纯Java 客户端类库,它使用一种与具体数据库无关的协议将数据库请求发送给服务器构件,然后该构件再将数据库请求翻译成数据库相关的协议。这简化了部署,因为平台相关的代码只位于服务器端。
- 驱动程序是纯Java 类库,它将JDBC 请求直接翻译成数据库相关的协议。
- JDBC 实现的目标
- 通过使用标准的SQL 语句,甚至是专门的SQL 扩展,程序员就可以利用Java 语言开发访问数据库的应用,同时还依旧遵守Java 语言的相关约定。
- 数据库供应商和数据库工具开发商可以提供底层的驱动程序。因此,他们可以优化各自数据库产品的驱动程序。
- Java 没有采用ODBC 模型的原因
- ODBC 很难学会。
- ODBC 中有几个命令需要配置很多复杂的选项,而在Java 编程语言中所采用的风格是要让方法简单而直观,但数量巨大。
- ODBC 依赖于void*指针和其他C 语言特性,而这些特性并不适用于Java 编程语言。
- 与纯Java 的解决方案相比,基于ODBC 的解决方案天生就缺乏安全性,且难于部署。
- JDBC 规范将驱动程序
-
JDBC 的典型用法
-
三层模型,它将可视化表示(位于客户端)从业务逻辑(位于中间层)和原始数据(位千数据库)中分离出来。客户端和中间层之间的通信在典型情况下是通过HTTP 来实现的。JDBC 管理着中间层和后台数据库之间的通信,以下为基本架构。
-
5.2 结构化查询语言
- SQL 是对所有现代关系型数据库都至关重要的命令行语言, JDBC 则使得我们可以通过SQL 与数据库进行通信。可以将JDB C 包看作是一个用千将S QL 语句传递给数据库的应用编程接口( API) 。
5.3 JDBC 配置
-
数据库URL
- JDBC 使用了一种与普通URL 相类似的语法来描述数据源。以下为示例
// Derby数据库 jdbc:derby://localhost:1527/COREJAVA;create=true // PostgreSQL数据库 jdbc:postgresql:COREJAVA // 一般语法,subprotocol 用于选择连接到数据库的具体驱动程序,other stuff参数的格式随所使用的subprotocol 不同而不同。 jdbc:subprotocol:other stuff
-
驱动程序JAR 文件
Derby驱动: derbyclient.jar PostgreSQL驱动: postgresql.jar ...
-
启动数据库
-
注册驱动器类
-
在Java 程序中加载驱动器类,这条语句将使得驱动器类被加载, 由此将执行可以注册驱动器的静态初始化器。
Class.forName("org.postgresql.Driver"); // force loading of driver class
-
设置jdbc.drivers 属性,可以用命令行参数来指定这个属性,或者在应用中用下面这样的调用来设置系统属性
java -Djdbc.drivers = org.postgresql.Driver ProgramName System.setProperty("jdbc.drivers", "org.postgresql.Driver"); // 可以提供多个驱动器 org.postgresql.Driver org.apache.derby.jdbc.ClientDriver
-
-
连接到数据库
// 数据库连接URL String url = "jdbc:postgresql:COREJAVA"; // 数据库名称 String username = "dbuser"; // 数据库密码 String password = "secret"; // 连接数据库 Connection conn = DriverManager.getConnection(url, username, password);
5.4 使用 JDBC 语句
-
执行SQL 语句
-
在执行S QL 语句之前,首先需要创建一个Statement 对象。要创建Statement 对象,需要使用调用DriverManager.getConnection 方法所获得的Connection 对象。调用Statement 接口中的executeUpdate
Statement stat = conn.createStatement(); stat.executeUpdate(command);
executeUpdate 方法将返回受SQL 语句影响的行数,或者对不返回行数的语句返回0。executeUpdate 方法既可以执行诸如INSERT 、UPDATE 和DELETE 之类的操作,也可以执行诸如CREATE TABLE 和DROP TABLE 之类的数据定义语句。
-
执行SELECT 查询时必须使用executeQuery 方法,另外还有一个execute 语句可以执行任意的SQL 语句,此方法通常只用千由用户提供的交互式查询。executeQuery 方法会返回一个ResultSet 类型的对象,可以通过它来每次一行地迭代遍历所有查询结果。
ResultSet rs = stat.executeQuery("SELECT * FROM Books"); while(rs.next()) { // look at a row of the result set }
-
结果集中行的顺序是任意排列的。除非使用ORDER BY 子句指定行的顺序,否则不能为行序强加任何意义。查看每一行时,可能希望知道其中每一列的内容,有许多访问器(accessor ) 方法可以用于获取这些信息。
String isbn = rs.getString(1); double price = rs.getDouble("Price");
-
不同的数据类型有不同的访问器,每个访问器都有两种形式, 一种接受数字型参数,另一种接受字符串参数。当使用数字型参数时,我们指的是该数字所对应的列。当get 方法的类型和列的数据类型不一致时,每个get 方法都会进行合理的类型转换。
-
-
-
管理连接、语句和结果集
-
每个Connection 对象都可以创建一个或多个Statement 对象。 一个Statement 对象最多只能有一个打开的结果集。如果需要执行多个查询操作,且需要同时分析查询结果,那么必须创建多个Statement 对象。
-
至少有一种常用的数据库(Microsoft SQL Server) 的JDBC 驱动程序只允许同时存在一个活动的Statement 对象。使用DatabaseMetaData 接口中的getMaxStatements 方法可以获取JDBC 驱动程序支持的同时活动的语句对象的总数。
-
使用完ResultSet 、Statement 或Connection 对象后,应立即调用close 方法。如果Statement 对象上有一个打开的结果集,那么调用close 方法将自动关闭该结果集。如果Statement 对象上有一个打开的结果集,那么调用close 方法将自动关闭该结果集。同样地,调用Connection 类的close 方法将关闭该连接上的所有语句。在使用JavaSE 7 时,可以在Statement 上调用closeOnCompletion方法,在其所有结果集都被关闭后,该语句会立即被自动关闭。
应该使用带资源的try 语句块来关闭连接,并使用一个单独的try/catch 块处理异常。分离try 程序块可以提高代码的可读性和可维护性。
-
-
分析SQL 异常
-
每个 SQLException 都有一个由多个 SQLException 对象构成的链,这些对象可以通过 getNextException 方法获取。Java SE 6 改进了SQLException 类,让其实现了Iterable
接口,因此可以使用迭代器遍历所有的SQL异常。可以在SQLException 上调用 getSQLState 和 getErrorCode 方法来进一步分析它。 -
数据库驱动程序可以将非致命问题作为警告报告,我们可以从连接、语句和结果集中获取这些警告。SQLWarning 类是 SQLException 的子类,我们可以调用 getSQLState 和 getErrorCode 来获取有关警告的更多信息。
SQLWarning w = stat.getWarning(); while(w != null){ // do something with w w = w.nextWarning(); }
-
SQL异常类型
-
-
组装数据库
- ExecSQL 程序
- 连接数据库。getConnection 方法读取database.properties 文件中的属性信息,并将属性 jdbc.drivers 添加到系统属性中。驱动程序管理器使用属性 jdbc.drivers 加载相应的驱动程序。getConnection 方法使用jdbc.url 、jdbc.username 和 jdbc.password 等属性打开数据库连接。
- 使用SQL 语句打开文件。如果未提供任何文件名,则在控制台中提示用户输入语句。
- 使用泛化的execute 方法执行每条语句。如果它返回true, 则说明该语句产生了一个结果集。
- 如果产生了结果集, 则打印出结果。因为这是一个泛化的结果集,所以我们必须使用元数据来确定该结果的列数。
- 如果运行过程中出现SQL 异常, 则打印出这个异常以及所有可能包含在其中的与其链接在一起的相关异常。
- 关闭数据库连接。
- ExecSQL 程序
5.5 执行查询操作
-
预备语句
-
在预备查询语句中,每个宿主变量都用”?”来表示。如果存在一个以上的变量,那么在设置变量值时必须注意”?”的位置。在执行预备语句之前,必须使用 set 方法将变量绑定到实际的值上。和 ResultSet 接口中的 get 方法类似,针对不同的数据类型也有不同的set 方法。如果想要重用已经执行过的预备查询语句,那么除非使用set 方法或调用 clearParameters 方法,否则所有宿主变量的绑定都不会改变。这
String publisherQuery = "SELECT * "+ "FROM course " + "WHERE cname = ?"; // 创建预备语句 PreparedStatement stat = conn.prepareStatement(publisherQuery); // 将变量绑定到实际的值 // void setString(int parameterIndex, String x) stat.setString(1, cname); // 执行SQL语句 ResultSet rs = stat.executeQuery();
-
-
读写LOB
-
在SQL 中, 二进制大对象称为BLOB , 字符型大对象称为CLOB 。要读取LOB , 需要执行SELECT 语句,然后在ResultSet 上调用getBlob 或getClob方法,这样就可以获得Blob 或Clob 类型的对象。要从Blob 中获取二进制数据,可以调用 getBytes 或getBinaryStream 。
-
获取一张图像
stat.set(1, isbn); try (ResultSet result = stat.executeQuery()) { if (result.next()) { Blob coverBlob = result.getBlob(1); Image coverlmage = ImageIO.read(coverBlob.getBinaryStream()); } }
类似地,如果获取了Clob 对象,那么就可以通过调用getSubString 或getCharacterStream方法来获取其中的字符数据。
-
要将LOB 置于数据库中,需要在Connection 对象上调用createBlob 或createClob ,然后获取一个用于该LOB 的输出流或写出器,写出数据,并将该对象存储到数据库中。
Blob coverBlob = connection.createBlob(); int offset = 0; OutputStream out = coverBlob.setBinaryStream(offset); ImageIO.write(coverimage, "PNG", out); PreparedStatement stat = conn.prepareStatement("INSERT INTO Cover VALUES (?, ?)"); stat.set(1, isbn); stat.set(2, coverBlob); stat.executeUpdate();
-
-
SQL 转义
-
转义主要用千下列场景
- 日期和时间字面常量
- 调用标量函数
- 调用存储过程
- 外连接
- 在LIKE 子句中的转义字符
-
日期和时间字面常量随数据库的不同而变化很大。使用d 、t 、ts 来表示DATE 、TIME和TIME STAMP 值
{d '2008-01-24'} {t '23:59:59'} {ts'2008-01-24 23:59:59.999'}
-
标量函数(sca lar function ) 是指仅返回单个值的函数。在数据库中包含大量的函数,但是不同的数据库中这些函数名存在着差异。要调用函数, 需要像下面这样嵌入标准的函数名和参数
{fn left(?, 20)} {fn user()}
-
存储过程(stored procedure ) 是在数据库中执行的用数据库相关的语言编写的过程。要调用存储过程,需要使用call 转义命令,在存储过程没有任何参数时,可以不用加上括号。另外,应该用=来捕获存储过程的返回值
{call PROO(?, ?)} {call PROC2} {call ? = PROC3(?)}
-
最后一种情况,_和% 字符在LIKE 子句中具有特殊含义,用来匹配一个字符或一个字符序列。如果想要匹配所有包含_字符的字符串,就必须使用下面的结构
... WHERE ? LIKE %!_% {escape'!'} // 将!定义为转义字符,而 !_ 组合表示字面常量下划线。
-
-
多结果集
-
在执行存储过程,或者在使用允许在单个查询中提交多个SELECT 语句的数据库时, 一个查询有可能会返回多个结果集。获取所有结果集代码如下
- 使用execute 方法来执行SQL 语句。
- 获取第一个结果集或更新计数。
- 重复调用getMoreResults 方法以移动到下一个结果集。
- 当不存在更多的结果集或更新计数时,完成操作。
-
如果由多结果集构成的链中的下一项是结果集, execute 和getMoreResults 方法将返回true, 而如果在链中的下一项不是更新计数, getUpdateCount 方法将返回 -1 。
boolean isResult = stat.execute(command); boolean done = false; while (!done) { if (isResult) { ResultSet result = stat.getResultSet(); // dosomet比ng with resu it } else { int updateCount = stat.getUpdateCount(); if (updateCount >= 0) {
// do something with updateCount } else { done = true; } } if (!done) isResult = stat.getMoreResults();
}
-
-
获取自动生成的键
-
JDBC 没有提供独立于提供商的自动生成键的解决方案,但是它提供了获取自动生成键的有效途径。当我们向数据表中插入一个新行,且其键自动生成时,可以用下面的代码来获取这个键:
stat.executeUpdate(insertStatement, Statement.RETURN_GENERATED_KEYS); ResultSet rs = stat.getGeneratedKeys(); if (rs.next()) { int key = rs.getInt(1); }
-
5.6 可滚动和可更新的结果集
-
可滚动的结果集
-
从查询中获取可滚动的结果集
// 获取Statement Statement stat = conn.createStatement(type, concurrency); // 获取PreparedStatement PreparedStatement stat = conn.prepareStatement(command, type, concurrency);
-
type 和concurrency 的所有可能值
-
创建滚动查询
// 只想滚动遍历结果集,而不想编辑它的数据, Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY); // 通过调用以下方法获得的所有结果集都将是可滚动 ResultSet rs = stat.executeQuery(query); // 在结果集上滚动,向后滚动。 if (rs.previous()) ... // 将游标向后或向前移动多行,如果n 为正数,游标将向前移动。如果n 为负数,游标将向后移动。 rs.relative(n); // 将游标设置到指定的行号 rs.absolute(n); // 获取当前行的行号,结果集中第一行的行号为1 。 int currentRow = rs.getRow();
- first 、last 、beforeFirst 和afterlast 这些简便方法用于将游标移动到第一行、最后一行、第一行之前或最后一行之后。isFirst 、islast 、isBeforeFirst 和isAfterlast 用千测试游标是否位千这些特殊位置上。使用可滚动的结果集是非常简单的,将查询数据放入缓存中的复杂工作是由数据库驱动程序在后台完成的。
非所有的数据库驱动程序都支持可滚动和可更新的结果集。(使用DatabaseMetaData接口中的supportsResultSetType 和supportsResultSetConcurrency 方法,我们可以荻知在使用特定的驱动程序时,某个数据库究竟支持哪些结果集类型以及哪些并发模式
-
-
可更新的结果集
-
若需要编辑结果集中的数据,并且将结果集上的数据变更自动反映到数据库中,那么就必须使用可更新的结果集。
Statement stat = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_UPDATABLE); // 调用executeQuery 方法返回的结果集就将是可更新的结果集。
并非所有的查询都会返回可更新的结果集。如果查询涉及多个表的连接操作,那么它所产生的结果集将是不可更新的。如果查询只涉及一个表,或者在查询时是使用主键连接多个表的,那么它所产生的结果集将是可更新的结果集。可以调用ResultSet 接口中的getConcurrency 方法来确定结果集是否是可更新的。
- 对应于S QL 类型的数据类型都配有updateXxx 方法,比如updateDouble 、updateString 等。与getXxx 方法相同,在使用updateXxx 方法时必须指定列的名称或序号。然后,你可以给该字段设置新的值。
- updateXxx 方法改变的只是结果集中的行值,而非数据库中的值。当更新完行中的字段值后,必须调用updateRow 方法,这个方法将当前行中的所有更新信息发送给数据库。
-
在数据库中添加一条新的记录
- 使用moveToinsertRow 方法将游标移动到特定的位置
- 调用updateX.xx 方法在插入行的位置上创建一个新的行。
- 调用insertRow 方法将新建的行发送给数据库。
- 调用moveToCurrentRow 方法将游标移回到调用moveToinsertRow 方法之前的位置
// 示例程序 rs.moveToInsertRow(); rs.updateString("Title", title); rs.updateString("ISBN", isbn); rs.updateString("Pub1isher_Id", pubid); rs.updateDouble("Price", price); rs.insertRow(); rs.moveToCurrentRow();
在插入行中没有指定值的列,将被设置为S QL 的NULL 。但是,如果这个列有NOT NULL 约束,那么将会抛出异常,而这一行也无法插入。
-
删除行
- 删除游标所指的行
rs.deleteRow();
deleteRow 方法会立即将该行从结果集和数据库中删除。 - ResultSet 接口中的updateRow 、insertRow 和deleteRow 方法的执行效果等同于SQL 命令中的UPDATE 、INSERT 和DELETE 。不过,习惯千Java 编程语言的程序员通常会觉得使用结果集来操控数据库要比使用SQL 语句自然得多。
- 删除游标所指的行
如果不小心处理的话,就很有可能在使用可更新的结果集时编写出非常低效的代码。执行UPDATE 语句,要比建立一个查询,然后一边遍历一边修改数据显得高效得多。对于用户能够任意修改数据的交互式程序来说,使用可更新的结果集是非常有意义的。,,但是,对大多数程序性的修改而言,使用SQL 的UPDATE 语句更合适一些。
-
5.7 行集
- 可滚动的结果集需要在与用户的整个交互过程中,必须始终与数据库保持连接。用户也许会离开电脑旁很长一段时间,而在此期间却始终占有着数据库连接。在这种情况下,我们可以使用行集。RowSet 接口扩展自ResultSet 接口,却无需始终保持与数据库的连接。
-
构建行集
-
javax.sql.rowset 包提供的接口
- CachedRowSet 允许在断开连接的状态下执行相关操作。
- WebRowSet 对象代表了一个被缓存的行集,该行集可以保存为XML 文件。该文件可以移动到Web 应用的其他层中,只要在该层中使用另一个WebRowSet 对象重新打开该文件即可。
- Fil teredRowSet 和JoinRowSet 接口支持对行集的轻量级操作,它们等同于SQL 中的SELECT 和JOIN 操作。这两个接口的操作对象是存储在行集中的数据,因此运行时无需建立数据库连接。
- JdbcRowSet 是ResultSet 接口的一个瘦包装器。它在RowSet 接口中添加了有用的方法。
-
获取行集
RowSetFactory factory = RowSetProvider.newFactory(); CachedRowSet crs = factory.createCachedRowSet(); // 获取其他行集类型的对象也有类似的方法。 // 或者使用对应的实现类创建 CachedRowSet crs = new com.sun.rowset.CachedRowSetImpl();
-
-
被缓存的行集
-
一个被缓存的行集中包含了一个结果集中所有的数据。CachedRowSet 是ResultSet接口的子接口,所以你完全可以像使用结果集一样来使用被缓存的行集。被缓存的行集有一个非常重要的优点:断开数据库连接后仍然可以使用行集。
-
可以修改被缓存的行集中的数据,但这些修改不会立即反馈到数据库中,必须发起一个显式的请求,以便让数据库真正接受所有修改。
// 使用一个结果集来填充CachedRowSet 对 ResultSet result =...; RowSetFactory factory = RowSetProvider.newFactory(); CachedRowSet crs = factory.createCachedRowSet(); crs.populate(result); conn.close(); // now OK to close the database connection // 或者 // 让CachedRowSet 对象自动建立一个数据库连 crs.setURL("jdbc:derby://localhost:1527/COREJAVA"); crs.setUsername("dbuser"); crs.setPassword("secret"); // 设置查询语句和所有参数 crs. setCommand("SELECT • FROM Books WHERE Publisher _ID = ?"); crs.setString(1, publisherld); // 将查询结果填充到行集,这个方法调用会建立数据库连接、执行查询操作、填充行集。 crs.execute();
查询结果非常大可以分页
// 只获得20 行数 CachedRowSet crs =... ; crs.setCommand(command); crs.setPageSize(20); crs.execute(); // 获取下一批数据 crs.nextPage();
- 使用与结果集中相同的方法来查看和修改行集中的数据,调用以下方法将修改写回到数据库
crs.acceptChanges(conn); // 下面方法 在行集中设置了连接数据库所需的信息才可以调用 crs.acceptChanges();
-
使用结果集来填充行集,那么行集就无从获知需要更新数据的数据库表名。此时,必须调用setTable 方法来设置表名称。
-
在填充了行集之后,数据库中的数据发生了改变,这显然容易造成数据不一致性。为了解决这个问题,参考实现会首先检查行集中的原始值(即,修改前的值)是否与数据库中的当前值一致。如果一致,那么修改后的值将覆盖数据库中的当前值。否则,将抛出SyncProviderException 异常,且不向数据库写回任何值。
-
5.8 元数据
- 在SQL 中,描述数据库或其组成部分的数据称为元数据。我们可以获得三类元数据: 关千数据库的元数据、关千结果集的元数据以及关千预备语句参数的元数据。
-
DatabaseMetaData接口用千提供有关数据库的数据,该结果集中的每一行都包含了数据库中一张表的详细信息,
DatabaseMetaData meta = conn.getMetaData(); // 迭代该数据集 while (mrs.next()) { tableNames.additem(mrs.getString(3)); }
- DatabaseMetaData 接口中有上百个方法可以用于查询数据库的相关信息,包括一些使用奇特的名字进行调用的方法。这些方法主要是针对有特殊要求的高级用户的,尤其是那些需要编写涉及多个数据库且具有高可移植性的代码的编程人员。
-
ResultSetMetaData 接口用于提供结果集的相关信息。每当通过查询得到一个结果集时,我们都可以获取该结果集的列数以及每一列的名称、类型和字段宽度。
ResultSet rs = stat.executeQuery("SELECT * FROM " + tableName); ResultSetMetaData meta = rs.getMetaData(); for (int i = 1; i <= meta.getColumnCount(); i++) { String columnName = meta.getColumnLabel(i); int columnWidth = meta.getColumnDisplaySize(i); }
5.9 事务
- 我们可以将一组语句构建成一个事务(transaction ) 。当所有语句都顺利执行之后,事务可以被提交(commit) 。否则,如果其中某个语句遇到错误,那么事务将被回滚,就好像没有任何语句被执行过一样。将多个语句组合成事务的主要原因是为了确保数据库完整性( database integrity)。
-
用JDBC 对事务编程
-
数据库连接处千自动提交模式(autocommit mode) 。每个SQL 语句一旦被执行便被提交给数据库。一旦命令被提交,就无法对它进行回滚操作。
-
使用事务
// 关闭默认提交 conn.setAutoCommit(false); // 使用通常的方法创建一个语句对 Statement stat = conn.createStatement(); // 任意多次地调用executeUpdate 方法 stat.executeUpdate(command1); stat.executeUpdate(command2); ... // 调用commit 方法提交事务 conn.commit(); // 出现错误,则调用rollback方法,程序将自动撤销自上次提交以来的所有语句 conn.rollback();
-
-
保存点
-
在使用某些驱动程序时,使用保存点(save point ) 可以更细粒度地控制回滚操作。创建一个保存点意味着稍后只需返回到这个点,而非事务的开头。
Statement stat = conn.createStatement(); // start transaction ; roll back () goes he re stat.executeUpdate(commandl); Savepoint svpt = conn.setSavepoint(); // set save point; roll bac k(svpt) goes here stat.executeUpdate(command2); if (...)conn.rollback(svpt); // undo effect of command2 conn.commit(); // 不再需要保存点时,必须释放它: conn.releaseSavepoint(svpt);
-
-
批量更新
-
假设有一个程序需要执行许多INSERT 语句,以便将数据填入数据库表中,此时可以使用批量更新的方法来提高程序性能。在使用批量更新( batch update) 时,一个语句序列作为一批操作将同时被收集和提交。
使用DatabaseMetaData 接口中的supportsBatchUpdates 方法可以荻知数据库是否支持这种特性。
-
处于同一批中的语旬可以是INSERT 、UPDATE 和DELETE 等操作,也可以是数据库定义语句,如CREATE TABLE 和DROP TABLE 。在批量处理中添加SELECT 语旬会抛出异常。
-
执行批量处
-
创建一个Statement 对象
Statement stat = conn.createStatement();
-
调用addBatch 方法,而非executeUpdate 方法
String command = "CREATE TABLE ... " stat.addBatch(command); while (...) { command = "INS ERT INTO ... VALUES (" + ... + ")"; stat.addBatch(command); } // 提交整个批量更新语,将为所有已提交的语句返回一个记录数的数组。 int[] counts = stat.executeBatch();
-
为了在批量模式下正确地处理错误,必须将批量执行的操作视为单个事务。如果批量更新在执行过程中失败,那么必须将它回滚到批量操作开始之前的状态。
boolean autoCommit = conn.getAutoCommit(); conn.setAutoCommit(false); Statement stat = conn.createStatement(); // keep calling stat. add Batch(...) ; stat.executeBatch(); conn.commit(); conn.setAutoCommit(autoCommit);
-
-
5.10 高级SQL 类型
- JDBC 支持的SQL 数据类型以及它们在Java 语言中对应的数据类型。
-
SQL ARRAY ( SQL 数组)指的是值的序列。例如, Student 表中通常都会有一个Scores列,这个列就应该是ARRAY OF INTEGER (整数数组) 。getArray 方法返回一个接口类型为 java.sql.Array 的对象,该接口中有许多方法可以用于获取数组的值。
-
从数据库中获得一个LOB 或数组并不等于获取了它的实际内容,只有在访间具体的值时它们才会从数据库中被读取出来。这对改善性能非常有好处,因为通常这些数据的数据量都非常大。
-
某些数据库支持描述行位置的ROWID 值,这样就可以非常快捷地获取某一行值。JDBC 4 引入了java . sql.Rowld 接口,并提供了用于在查询中提供行ID , 以及从结果中获取该的方法。
-
国家属性字符串( NCHAR 及其变体)按照本地字符编码机制存储字符串,并使用本地排序惯例对这些字符串进行排序。JDBC 4 提供了方法,用千在查询和结果中进行Java 的String对象和国家属性字符串之间的双向转换。
-
有些数据库可以存储用户自定义的结构化类型。JDBC 3 提供了一种机制用千将SQL 结构化类型自动映射成Java 对象。
-
有些数据库提供用千XML 数据的本地存储。JDBC 4 引入了SQLXML 接口,它可以在内部的XML 表示和DOM 的Source/Result 接口或二进制流之间起到中介作用。
5.11 Web 与企业应用中的连接管理
-
使用database . propert i es 文件可以对数据库连接进行非常简单的设置。这种方法适用于小型的测试程序,但是不适用千规模较大的应用。
-
在Web 或企业环境中部署JDB C 应用时,数据库连接管理与Java 名字和目录接口( JNDI ) 是集成在一起的。遍布企业的数据源的属性可以存储在一个目录中,采用这种方式使得我们可以集中管理用户名、密码、数据库名和JDBCURL
Context jndiContext = new InitialContext(); DataSource source = (DataSource) jndiContext.lookup("java:comp/env/jdbc/corejava"); Connection conn = source.getConnection();
我们不再使用DriverManager , 而是使用 JNDI 服务来定位数据源。
API注释
5.3.5 连接到数据库
java.sql.DriverManager 1.1
static Connection getConnection(String url, String user, String password)
// 建立一个到指定数据库的连接,并返回一个Connection 对象。
java.sql.Connection 1.1
Statement createStatement()
// 创建一个Statement 对象, 用以执行不带参数的SQL 查询和更新。
void close()
// 立即关闭当前的连接,并释放由它所创建的JDB C 资源。
java.sql. Statement 1.1
ResultSet executeQuery(String sqlQuery)
// 执行给定字符串中的SQL 语句,并返回一个用千查看查询结果的ResultSet 对象。
int executeUpdate(String sqlStatement)
long executelargeUpdate(String sqlStatement) 8
// 执行字符串中指定的INSERT 、UPDATE 或DELETE 等SQL 语句。还可以执行数据定义语言( Data Definition Language, DDL ) 的语句,如CREATE TABLE 。返回受影响的行数,如果是没有更新计数的语句, 则返回0 。
boolean execute(String sqlStatement)
// 执行字符串中指定的SQL 语句。可能会产生多个结果集和更新计数。如果第一个执行结果是结果集, 则返回true ; 反之,返回false 。调用getResultSet 或getUpdateCount 方法可以得到第一个执行结果。请参见5 . 5 .4 节中关于处理多结果集的详细信息。
ResultSet getResultSet()
// 返回前一条查询语句的结果集。如果前一条语句未产生结果集,则返回null 值。对于每一条执行过的语句,该方法只能被调用一次。
int getUpdateCount()
long getLargeUpdateCount() 8
// 返回受前一条更新语句影响的行数。如果前一条语句未更新数据库,则返回 -1。对于每一条执行过的语句,该方法只能被调用一次。
void close()
// 关闭Statement 对象以及它所对应的结果集。
boolean isClosed() 6
// 如果语句被关闭, 则返回true 。
void closeOnCompletion() 7
// 使得一旦该语句的所有结果集都被关闭, 则关闭该语句。
java.sql.ResultSet 1.1
boolean next()
// 将结果集中的当前行向前移动一行。如果已经到达最后一行的后面, 则返回false 。注意,初始情况下必须调用该方法才能转到第一行。
Xxx getXxx(int columnNumber)
Xxx getXxx(String columnlabel)
// (Xxx 指数据类型, 例如; nt 、double 、String 和Date 等。)
<T> T getObject(int columnindex, Class<T> type) 7
<T> T getObject(String columnLabel, Class<T> type) 7
void updateObject(int columnIndex, Object x, SQLType targetSqlType) 8
void updateObject(String columnLabel, Object x, SQLType targetSqlType) 8
// 用给定的列序号或列标签返回或更新该列的值,并将值转换成指定的类型。列标签是SQL 的AS 子句中指定的标签,在没有使用AS 时,它就是列名。
int findColumn(String columnName)
// 根据给定的列名,返回该列的序号。
void close()
// 立即关闭当前的结果集。
boolean isClosed() 6
// 如果该语句被关闭,则返回true 。
5.4.3 分析SQL 异常
java.sql.SQLException 1.1
SOLException getNextException()
// 返回链接到该SQL 异常的下一个SQL 异常,或者在到达链尾时返回null 。
Iterator<Throwable> iterator() 6
// 获取迭代器,可以迭代链接的SQL 异常和它们的成因。
String getSQLState()
// 获取“SQL 状态",即标准化的错误代码。
int getErrorCode()
// 获取提供商相关的错误代码。
java.sql.SQLWarning 1.1
SQLWarning getNextWarning()
// 返回链接到该警告的下一个警告,或者在到达链尾时返回null 。
java.sql.Connection 1.1
java.sql.Statement 1.1
java.sql.ResultSet 1.1
SQLWarning getWarnings()
// 返回未处理警告中的第一个,或者在没有未处理警告时返回null 。
java.sql.Data Truncation 1.1
boolean getParameter()
// 如果在参数上进行了数据截断,则返回true; 如果在列上进行了数据截断,则返回false 。
int getIndex()
// 返回被截断的参数或列的索引。
int getDataSize()
// 返回应该被传输的字节数量,或者在该值未知的情况下返回-1。
int getTransferSize()
// 返回实际被传输的字节数量,或者在该值未知的情况下返回-1。
5.5.1 预备语句
java.sql.Connection 1.1
PreparedStatement prepareStatement(String sql)
// 返回一个含预编译语句的PreparedStatement 对象。字符串sql 代表一个SQL 语句,该语句可以包含一个或多个由?字符指明的参数占位符。
java.sal.PreparedStatement
void setXxx(int n, Xxx x)
// (Xxx 指i nt 、double 、String 、Date 之类的数据类型)设置第n 个参数值为x 。
void clearParameters()
// 清除预备语句中的所有当前参数。
ResultSet executeQuery()
// 执行预备SQL 查询,并返回一个Result Set 对象。
int executeUpdate()
// 执行预备SQL 语句INSERT 、UPDATE 或DELETE , 这些语句由PreparedStatement对象表示。该方法返回在执行上述语句过程中所有受影响的记录总数。如果执行的是数据定义语言(DDL) 中的语句,如CREATE TABLE, 则该方法返回0 。
5.5.2 读写LOB
java.sql.ResultSet 1.1
Blob getBlob(int columnlndex) 1 . 2
Blob getBlob(String columnlabel) 1.2
Clob getClob(int columnlndex) 1.2
Clob getClob(String columnlabel) 1.2
// 获取给定列的BLOB 或CLOB 。
java.sql.Blob 1.2
long length()
// 获取该BLOB 的长度。
byte[] getBytes(long startposition, long length)
// 获取该BLOB 中给定范围的数据。
InputStream getBinaryStream()
InputStream getBinaryStream(long startPosition, long length)
// 返回一个输入流,用于读取该BLOB 中全部或给定范围的数据。
OutputStream setBinaryStream(long startPosition) 1 . 4
// 返回一个输出流,用千从给定位置开始写入该BLOB 。
java.sql.Clob 1.2
long length()
// 获取该CLOB 中的字符总数。
String getSubString(long startPosition, long length)
// 获取该CLOB 中给定范围的字符。
Reader getCharacterStream()
Reader getCharacterStream(long startPosition, long length )
// 返回一个读入器(而不是流),用于读取CLOB 中全部或给定范围的数据。
Writer setCharacterStream(long startPosition) 1 . 4
// 返回一个写出器(而不是流),用千从给定位置开始写入该CL OB 。
java.sql.Connection 1.1
Blob createBlob() 6
Clob createClob() 6
// 创建一个空的BLOB 或CLOB 。
5.5.4 多结果集
java.sql. Statement 1.1
boolean getMoreResults()
boolean getMoreResults(int current) 6
// 获取该语句的下一个结果集, Current 参数是CLOSE_CURRENT_RESULT (默认值),KEEP_CURRENT_RESULT 或CLOSE_ALL_RESULTS 之一。如果存在下一个结果集, 并且它确实是一个结果集, 则返回true 。
5.5.5 获取自动生成的键
java.sql.Statement 1.1
boolean execute(String statement, int autogenerated) 1.4
int executeUpdate(String statement, int autogenerated) 1.4
// 像前面描述的那样执行给定的SQL 语句, 如果autogenerated 被设置为Statement.RETURN_GENERATED_KEYS, 并且该语句是一条INSERT 语句,那么第一列中就是自动生成的键。
5.6.2 可更新的结果集
java.sql.Connection 1.1
Statement createStatement(int type, int concurrency) 1 . 2
PreparedStatement prepareStatement(String command, int type, int concurrency) 1 .2
// 创建一个语句或预备语句,且该语句可以产生指定类型和并发模式的结果集。
// 参数: command 要预备的命令
// type ResultSet 接口中的下述常量之一: TYPE_FORWARD_ONLY 、TYP_SCROLL_INSENSITIVE 或者 TYPE_SCROLL_SENSITIVE
// concurrency ResultSet 接口中的下述常量之一: CONCUR_READ_ONLY或者CONCUR_UPDATABLE
java.sql.ResultSet 1.1
int getType() 1.2
// 返回结果集的类型。返回值为以下常量之一: TYPE_FORWARD_ONLY 、TYPE_SCROLL_INSENSITIVE 或TYPE_SCROLL_SENS ITI VE 。
int getConcurrency() 1.2
// 返回结果集的并发设置。返回值为以下常量之一: CONCUR_READ_ONLY 或CONCUR_UPDATABLE
boolean previous() 1.2
// 将游标移动到前一行。如果游标位千某一行上, 则返回true; 如果游标位千第一行之前的位置, 则返回false 。
int getRow() 1.2
// 得到当前行的序号。所有行从1 开始编号。
boolean absolute(int r) 1.2
// 移动游标到第r 行。如果游标位千某一行上, 则返回true 。
boolean relative(int d) 1.2
// 将游标移动d 行。如果d 为负数, 则游标向后移动。如果游标位于某一行上, 则返回true 。
boolean first() 1.2
boolean last() 1.2
// 移动游标到第一行或最后一行。如果游标位千某一行上,则返回true 。
void beforeFirst() 1.2
void afterlast() 1.2
// 移动游标到第一行之前或最后一行之后的位置。
boolean isFirst() 1.2
boolean islast() 1.2
// 测试游标是否在第一行或最后一行。
boolean isBeforeFirst() 1.2
boolean isAfterlast() 1.2
// 测试游标是否在第一行之前或最后一行之后的位置。
void moveToinsertRow() 1.2
// 移动游标到插入行。插入行是一个特殊的行,可以在该行上使用updateXxx 和insertRow方法来插入新数据。
void moveToCurrentRow() 1.2
// 将游标从插入行移回到调用moveTolnsertRow 方法之前它所在的那一行。
void insertRow() 1.2
// 将插入行上的内容插入到数据库和结果集中。
void deleteRow() 1.2
// 从数据库和结果集中删除当前行。
void updateXxx(int column, Xxx data) 1.2
void updateXxx(String columnName, Xxx data) 1.2
// (X江指数据类型,比如int 、double 、String 、Date 等)更新结果中当前行上的某个字段值。
void updateRow() 1.2
// 将当前行的更新信息发送到数据库。
void cancelRowUpdates() 1.2
// 撤销对当前行的更新。
java.sql. DatabaseMetaData 1.1
boolean supportsResultSetType(int type) 1.2
// 如果数据库支待给定类型的结果集, 则返回true 。type 是ResultSet 接口中的常量之一:TYP E_FORWARD_ONLY 、TYPE_SCROLLINSENSITIVE 或者TYPE_SCROLLSENSITIVE 。
boolean supportsResultSetConcurrency(int type, int concurrency) 1.2
// 如果数据库支持给定类型和并发模式的结果集, 则返回true 。
// 参数: type ResultSet 接口中的下述常量之一: TYPE_FORWARD_ONLY 、TYP_SCROLL_INSENSITIVE 或者 TYPE_SCROLL_SENSITIVE
// concurrency ResultSet 接口中的下述常量之一: CONCUR_READ_ONLY或者CONCUR_UPDATABLE
5.7.2 被缓存的行集
javax.sql.RowSet 1.4
String getURL()
void setURL(String url)
// 获取或设置数据库的URL 。
String getUsername()
void setUsername(String username)
// 获取或设置连接数据库所需的用户名。
String getPassword()
void setPassword(String password)
// 获取或设置连接数据库所需的密码。
String getCommand()
void setCommand(String command)
// 获取或设置向行集中填充数据时需要执行的命令。
void execute()
// 通过执行使用setCommand 方法设置的语句集来填充行集。为了使驱动管理器可以获得连接,必须事先设定URL 、用户名和密码。
javax.sql.rowset. CachedRowSet 5.0
void execute(Connection conn)
// 通过执行使用setCommand 方法设置的语句集来填充行集。该方法使用给定的连接,并负责关闭它。
void populate(ResultSet result)
// 将指定的结果集中的数据填充到被缓存的行集中。
String getTableName()
void setTableName(String tableName)
// 获取或设置数据库表名称,填充被缓存的行集时所需的数据来自该表。
int getPageSize()
void setPageSize(int size)
// 获取和设置页的尺寸。
boolean nextPage()
boolean previousPage()
// 加载下一页或上一页, 如果要加载的页存在,则返回true 。
void acceptChanges()
void acceptChanges (Connection conn)
// 重新连接数据库,并写回行集中修改过的数据。如果因为数据库中的数据已经被修改而导致无法写回行集中的数据,该方法可能会抛出SyncProviderException 异常。
javax.sql.rowset.RowSetProvider 7
static RowSetFactory newFactory()
// 创建一个行集工厂。
CachedRowSet createCachedRowSet()
FilteredRowSet createFilteredRowSet()
JdbcRowSet createJdbcRowSet()
JoinRowSet createJoinRowSet()
WebRowSet createWebRowSet()
// 创建一个指定类型的行集。
5.8 元数据
java.sql.Connection 1.1
DatabaseMetaData getMetaData()
// 返回一个DatabaseMetaData 对象,该对象封装了有关数据库连接的元数据。
java.sql. DatabaseMetaData 1.1
ResultSet getTables(String catalog, String schemaPattern, String tableNamePattern, String types[])
// 返回某个目录(catalog) 中所有表的描述,该目录必须匹配给定的模式(schema) 、表名字模式以及类型标准。(模式用千描述一组相关的表和访问权限,而目录描述的是一组相关的模式,这些概念对组织大型数据库非常重要。)
// catalog 和schema 参数可以为"",用千检索那些没有目录或模式的表。如果不想考虑目录和模式,也可以将上述参数设为null 。
// types 数组包含了所需的表类型的名称,通常表类型有TABLE 、VIEW 、SYSTEM、TABLE 、GLOBAL TEMPORARY 、LOCAL TEMPORARY 、ALIAS 和SYNONYM 。如果types为null , 则返回所有类型的表。
// 返// 回的结果集共有5 列,均为String 类型。
// 行 名称 解释
// 1 TABLE_CAT 表目录(可以为null )
// 2 TABLE_SCHEM 表模式(可以为null )
// 3 TABLE_ NAME 表名称
// 4 TABLE_TYPE 表类型
// 5 REMARKS 关千表的注释
int getJDBCMajorVersion() 1 . 4
int getJDBCMinorVersion() 1.4
// 返回建立数据库连接的JDB C 驱动程序的主版本号和次版本号。例如, 一个JDBC 3.0的驱动程序有一个主版本号3 和一个次版本号0 。
int getMaxConnections()
// 返回可同时连接到数据库的最大并发连接数。
int getMaxStatements()
// 返回单个数据库连接允许同时打开的最大并发语句数。如果对允许打开的语句数目没有限制或者不可知, 则返回0 。
java.sql.ResultSet 1.1
ResultSetMetaData getMetaData()
// 返回与当前ResultSet 对象中的列相关的元数据。
java.sql. ResultSetMetaData 1.1
int getColumnCount ()
// 返回当前ResultSet 对象中的列数。
int getColumnDisplaySize(int column)
// 返回给定列序号的列的最大宽度。
// 参数: column 列序号
String getColumnlabel(int column)
// 返回该列所建议的名称。
// 参数: column 列序号
String getColumnName(int column)
// 返回指定的列序号所对应的列名。
// 参数: column 列序号
5.9.3 批量更新
java.sql.Connection 1.1
boolean getAutoCommit()
void setAutoCommit(boolean b)
// 获取该连接中的自动提交模式,或将其设置为b 。如果自动更新为true, 那么所有语句将在执行结束后立刻被提交。
void commit()
// 提交自上次提交以来所有执行过的语句。
void rollback()
// 撤销自上次提交以来所有执行过的语句所产生的影响。
Savepoint setSavepoint() 1.4
Savepoint setSavepoint(String name) 1. 4
// 设置一个匿名或具名的保存点。
void rollback(Savepoint svpt) 1.4
// 回滚到给定保存点。
void releaseSavepoint(Savepoint svpt) 1.4
// 释放给定的保存点。
java.sql.Savepoint 1.4
int getSavepointld()
// 获取该匿名保存点的ID 号。如果该保存点具有名字,则抛出一个SQLException异常。
String getSavepointName()
// 获取该保存点的名称。如果该对象为匿名保存点,则抛出一个SQLException 异常。
java.sql. Statement 1.1
void addBatch(String command) 1.2
// 添加命令到该语句当前的批量命令中。
int[] executeBatch() 1.2
long[] executeLargeBatch() 8
// 执行当前批量更新中的所有命令。返回一个记录数的数组,其中每一个元素都对应一条语句, 如果其值非负,则表示受该语句影响的记录总数;如果其值为SUCCESS_NO_INFO , 则表示该语句成功执行了,但没有记录数可用;如果其值为EXECUTE_FAILED , 则表示该语句执行失败了。
java.sql.DatabaseMetaData 1.1
boolean supportsBatchUpdates() 1.2
// 如果驱动程序支持批量更新,则返回true 。
评论区