简答题 应用文写作简答题JDBC技术对如何保证多条SQL命令要么都成功,要么都失败。 求Java大神

spring jdbc和spring mybatis没什么很大的区别,为什么要用mybatis优势在哪里?
<a data-traceid="question_detail_above_text_l&&
同样都是要写sql,spring提供的JDBCTemplate也能将数据转换成对象,将对象插入到数据库,那使用spring时为什么还要用mybatis呢?直接用spring提示的JDBC不就行了,效率更高,mybatis的优势在哪里?
建议你看看mybatis的文档,有中文版,mybatis再做相对大型的系统,还是有一定优势的。
比如sql重用,缓存的集成等。而且代码量也相对较小.
如果是小规模的项目,spring jdbc足矣
好人做到底,给你个传送门:
mybatis有DAO模版,强大的代码生成工具。
--- 共有 2 条评论 ---
说的是SqlSessionTemplate吗?
JDBCTemplate其实也很不错的。
你说的那个模板是什么?能说下嘛
我主要是喜欢 mybiats 的动态sql &这个还是真不错的 其他的就一般了
--- 共有 2 条评论 ---
用了spring jdbc,那系统里就有俩套存取方法了。只用mybatis的话,可以保持系统的纯洁,更能将sql开发人员和java开发人员的任务分开。一般的话,都无所谓,搞CRUD的人很少会考虑其他问题
动态SQL?能举个例子吗?
动态sql &spring也可以支持,需要加个其他的包!个人觉得,用spring再用mybiats纯属个脱裤子放屁!当然很多人很乐意这么做!
--- 共有 2 条评论 ---
: freemarker包,或者xml文档,但是你要会解析!
什么包?可不可以像Hibernate或Mybatis那样可以单独使用配置文件?
因为现在人都越来越懒了。
spring的jdbc,如果把sql挪出去单独维护就很不错了。当然MyBatis的SqlMapper更有语义性。
另外MyBatis提供了延迟加载方法。
cache部分现在可以单独维护,MyBatis的亮点确实变少了很多。
我仔细看了一下文档,发现mybatis确实还是有一些特性的。谢谢各位
引用来自“恺哥”的答案 建议你看看mybatis的文档,有中文版,mybatis再做相对大型的系统,还是有一定优势的。
比如sql重用,缓存的集成等。而且代码量也相对较小.
如果是小规模的项目,spring jdbc足矣
好人做到底,给你个传送门: 谢谢你的传送,我已到达目的地,这个文档有没有PDF版的?我想下载Java DB 和 JDBC 4.0
此内容是该系列 # 部分中的第 # 部分: Java SE 6 新特性http://www.ibm.com/developerworks/cn/java/j-lo-jse6/敬请期待该系列的后续内容。此内容是该系列的一部分:Java SE 6 新特性敬请期待该系列的后续内容。
长久以来,由于大量(甚至几乎所有)的 Java 应用都依赖于数据库,如何使用 Java 语言高效、可靠、简洁地访问数据库一直是程序员们津津乐道的话题。新发布的 Java SE 6 也在这方面更上层楼,为编程人员提供了许多好用的新特性。其中最显著的,莫过于 Java SE 6 拥有了一个内嵌的 100% 用 Java 语言编写的数据库系统。并且,Java 6 开始支持 JDBC 4.0 的一系列新功能和属性。这样,Java SE 在对持久数据的访问上就显得更为易用和强大了。Java DB:Java 6 里的数据库新安装了 JDK 6 的程序员们也许会发现,除了传统的 bin、jre 等目录,JDK 6 新增了一个名为 db 的目录。这便是 Java 6 的新成员:Java DB。这是一个纯 Java 实现、开源的数据库管理系统(DBMS),源于 Apache 软件基金会(ASF)名下的项目 Derby。它只有 2MB 大小,对比动辄上 G 的数据库来说可谓袖珍。但这并不妨碍 Derby 功能齐备,支持几乎大部分的数据库应用所需要的特性。更难能可贵的是,依托于 ASF 强大的社区力量,Derby 得到了包括 IBM 和 Sun 等大公司以及全世界优秀程序员们的支持。这也难怪 Sun 公司会选择其 10.2.2 版本纳入到 JDK 6 中,作为内嵌的数据库。这就好像为 JDK 注入了一股全新的活力:Java 程序员不再需要耗费大量精力安装和配置数据库,就能进行安全、易用、标准、并且免费的数据库编程。在这一章中,我们将初窥 Java DB 的世界,来探究如何使用它编写出功能丰富的程序。Hello, Java DB:内嵌模式的 Derby既然有了内嵌(embedded)的数据库,就让我们从一个简单的范例(代码在
中列出)开始,试着使用它吧。这个程序做了大多数数据库应用都可能会做的操作:在 DBMS 中创建了一个名为 helloDB 的数据库;创建了一张数据表,取名为 hellotable;向表内插入了两条数据;然后,查询数据并将结果打印在控制台上;最后,删除表和数据库,释放资源。清单 1. HelloJavaDB 的代码public class HelloJavaDB {
public static void main(String[] args) {
try { // load the driver
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();
System.out.println("Load the embedded driver");
Connection conn =
Properties props = new Properties();
props.put("user", "user1");
props.put("password", "user1");
//create and connect the database named helloDB
conn=DriverManager.getConnection("jdbc:derby:helloDB;create=true", props);
System.out.println("create and connect to helloDB");
conn.setAutoCommit(false);
// create a table and insert two records
Statement s = conn.createStatement();
s.execute("create table hellotable(name varchar(40), score int)");
System.out.println("Created table hellotable");
s.execute("insert into hellotable values('Ruth Cao', 86)");
s.execute("insert into hellotable values ('Flora Shi', 92)");
// list the two records
ResultSet rs = s.executeQuery(
"SELECT name, score FROM hellotable ORDER BY score");
System.out.println("name\t\tscore");
while(rs.next()) {
StringBuilder builder = new StringBuilder(rs.getString(1));
builder.append("\t");
builder.append(rs.getInt(2));
System.out.println(builder.toString());
// delete the table
s.execute("drop table hellotable");
System.out.println("Dropped table hellotable");
rs.close();
s.close();
System.out.println("Closed result set and statement");
conn.commit();
conn.close();
System.out.println("Committed transaction and closed connection");
try { // perform a clean shutdown
DriverManager.getConnection("jdbc:derby:;shutdown=true");
} catch (SQLException se) {
System.out.println("Database shut down normally");
} catch (Throwable e) {
// handle the exception
System.out.println("SimpleApp finished");
}随后,我们在命令行(本例为 Windows 平台,当然,其它系统下稍作改动即可)下键入以下命令:清单 2. 运行 HelloJavaDB 命令java –cp .;%JAVA_HOME%\db\lib\derby.jar HelloJavaDB程序将会按照我们预想的那样执行, 是执行结果的一部分截屏:图 1. HelloJavaDB 程序的执行结果上述的程序和以往没什么区别。不同的是我们不需要再为 DBMS 的配置而劳神,因为 Derby 已经自动地在当前目录下新建了一个名为 helloDB 的目录,来物理地存储数据和日志。需要做的只是注意命名问题:在内嵌模式下驱动的名字应为 org.apache.derby.jdbc.EmbeddedDriver;创建一个新数据库时需要在协议后加入 create=true。另外,关闭所有数据库以及 Derby 的引擎可以使用以下代码:清单 3. 关闭所有数据库及 Derby 引擎DriverManager.getConnection("jdbc:derby:;shutdown=true");如果只想关闭一个数据库,那么则可以调用:清单 4. 关闭一个数据库DriverManager.getConnection("jdbc:derby:helloDB;shutdown=true ");这样,使用嵌入模式的 Derby 维护和管理数据库的成本接近于 0。这对于希望专心写代码的人来说不失为一个好消息。然而有人不禁要问:既然有了内嵌模式,为什么大多数的 DBMS 都没有采取这样的模式呢?不妨做一个小实验。当我们同时在两个命令行窗口下运行 HelloJavaDB 程序。结果一个的结果与刚才一致,而另一个却出现了错误,如
所示。图 2. 内嵌模式的局限错误的原因其实很简单:在使用内嵌模式时,Derby 本身并不会在一个独立的进程中,而是和应用程序一起在同一个 Java 虚拟机(JVM)里运行。因此,Derby 如同应用所使用的其它 jar 文件一样变成了应用的一部分。这就不难理解为什么在 classpath 中加入 derby 的 jar 文件,我们的示例程序就能够顺利运行了。这也说明了只有一个 JVM 能够启动数据库:而两个跑在不同 JVM 实例里的应用自然就不能够访问同一个数据库了。鉴于上述的局限性,和来自不同 JVM 的多个连接想访问一个数据库的需求,下一节将介绍 Derby 的另一种模式:网络服务器(Network Server)。网络服务器模式如上所述,网络服务器模式是一种更为传统的客户端/服务器模式。我们需要启动一个 Derby 的网络服务器用于处理客户端的请求,不论这些请求是来自同一个 JVM 实例,还是来自于网络上的另一台机器。同时,客户端使用 DRDA(Distributed Relational Database Architecture)协议连接到服务器端。这是一个由 The Open Group 倡导的数据库交互标准。 说明了该模式的大体结构。由于 Derby 的开发者们努力使得网络服务器模式与内嵌模式之间的差异变小,使得我们只需简单地修改
中的程序就可以实现。如 所示,我们在 HelloJavaDB 中增添了一个新的函数和一些字符串变量。不难看出,新的代码只是将一些在 上一节中特别指出的字符串进行了更改:驱动类为 org.apache.derby.jdbc.ClientDriver,而连接数据库的协议则变成了 jdbc:derby://localhost:1527/。这是一个类似 URL 的字符串,而事实上,Derby 网络的客户端的连接格式为:jdbc:derby://server[:port]/databaseName[;attributeKey=value]。在这个例子中,我们使用了最简单的本地机器作为服务器,而端口则是 Derby 默认的 1527 端口。图 3. Derby 网络服务器模式架构清单 5. 网络服务器模式下的 HelloJavaDBpublic class HelloJavaDB {
public static String driver = "org.apache.derby.jdbc.EmbeddedDriver";
public static String protocol = "jdbc:derby:";
public static void main(String[] args) {
// same as before
private static void parseArguments(String[] args) {
if (args.length == 0 || args.length & 1) {
if (args[0].equalsIgnoreCase("derbyclient")) {
framework = "derbyclient";
driver = "org.apache.derby.jdbc.ClientDriver";
protocol = "jdbc:derby://localhost:1527/";
}当然,仅仅有客户端是不够的,我们还需要启动网络服务器。Derby 中控制网络服务器的类是
org.apache.derby.drda.NetworkServerControl,因此键入以下命令即可。如果想了解 NetworkServerControl 更多的选项,只要把 start 参数去掉就可以看到帮助信息了。关于网络服务器端的实现,都被 Derby 包含在 derbynet.jar 里。清单 6. 启动网络服务器java -cp .;"C:\Program Files\Java\jdk1.6.0\db\lib\derby.jar";
"C:\Program Files\Java\jdk1.6.0\db\lib\derbynet.jar"
org.apache.derby.drda.NetworkServerControl start相对应的,网络客户端的实现被包含在 derbyclient.jar 中。所以,只需要在 classpath 中加入该 jar 文件,修改后的客户端就可以顺利地读取数据了。再一次尝试着使用两个命令行窗口去连接数据库,就能够得到正确的结果了。如果不再需要服务器,那么使用 NetworkServerControl 的 shutdown 参数就能够关闭服务器。更多至此,文章介绍了 Java SE 6 中的新成员:Java DB(Derby),也介绍了如何在内嵌模式以及网络服务器模式下使用 Java DB。当然这只是浅尝辄止,更多高级的选项还需要在 Sun 和 Derby 的文档中寻找。在这一章的最后,我们将简单介绍几个 Java DB 的小工具来加快开发速度。它们都位于 org.apache.derby.tools 包内,在开发过程中需要获取信息或者测试可以用到。ij:一个用来运行 SQL 脚本的工具;dblook:为 Derby 数据库作模式提取(Schema extraction),生成 DDL 的工具;sysinfo:显示系统以及 Derby 信息的工具类;JDBC 4.0:新功能,新 API如果说上一章介绍了 Java 6 中的一个新成员,它本来就存在,但是没有被加入进 JDK。那么这一章,我们将关注在 JDBC 4.0 中又增加了哪些新功能以及与之相对应的新 API。
自动加载驱动在 JDBC 4.0 之前,编写 JDBC 程序都需要加上以下这句有点丑陋的代码:清单 7. 注册 JDBC 驱动Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance();Java.sql.DriverManager 的内部实现机制决定了这样代码的出现。只有先通过 Class.forName 找到特定驱动的 class 文件,DriverManager.getConnection 方法才能顺利地获得 Java 应用和数据库的连接。这样的代码为编写程序增加了不必要的负担,JDK 的开发者也意识到了这一点。从 Java 6 开始,应用程序不再需要显式地加载驱动程序了,DriverManager 开始能够自动地承担这项任务。作为试验,我们可以将
中的相关代码删除,重新编译后在 JRE 6.0 下运行,结果和原先的程序一样。好奇的读者也许会问,DriverManager 为什么能够做到自动加载呢?这就要归功于一种被称为 Service Provider 的新机制。熟悉 Java 安全编程的程序员可能对其已经是司空见惯,而它现在又出现在 JDBC 模块中。JDBC 4.0 的规范规定,所有 JDBC 4.0 的驱动 jar 文件必须包含一个 java.sql.Driver,它位于 jar 文件的 META-INF/services 目录下。这个文件里每一行便描述了一个对应的驱动类。其实,编写这个文件的方式和编写一个只有关键字(key)而没有值(value)的 properties 文件类似。同样地,‘#’之后的文字被认为是注释。有了这样的描述,DriverManager 就可以从当前在 CLASSPATH 中的驱动文件中找到,它应该去加载哪些类。而如果我们在 CLASSPATH 里没有任何 JDBC 4.0 的驱动文件的情况下,调用
中的代码会输出一个 sun.jdbc.odbc.JdbcOdbcDriver 类型的对象。而仔细浏览 JDK 6 的目录,这个类型正是在 %JAVA_HOME%/jre/lib/resources.jar 的 META-INF/services 目录下的 java.sql.Driver 文件中描述的。也就是说,这是 JDK 中默认的驱动。而如果开发人员想使得自己的驱动也能够被 DriverManager 找到,只需要将对应的 jar 文件加入到 CLASSPATH 中就可以了。当然,对于那些 JDBC 4.0 之前的驱动文件,我们还是只能显式地去加载了。清单 8. 罗列本地机器上的 JDBC 驱动Enumeration&Driver& drivers = DriverManager.getDrivers();
while(drivers.hasMoreElements()) {
System.out.println(drivers.nextElement());
}RowId熟悉 DB2、Oracle 等大型 DBMS 的人一定不会对 ROWID 这个概念陌生:它是数据表中一个“隐藏”的列,是每一行独一无二的标识,表明这一行的物理或者逻辑位置。由于 ROWID 类型的广泛使用,Java SE 6 中新增了 java.sql.RowId 的数据类型,允许 JDBC 程序能够访问 SQL 中的 ROWID 类型。诚然,不是所有的 DBMS 都支持 ROWID 类型。即使支持,不同的 ROWID 也会有不同的生命周期。因此使用 DatabaseMetaData.getRowIdLifetime 来判断类型的生命周期不失为一项良好的实践经验。我们在
的程序获得连接之后增加以下代码,便可以了解 ROWID 类型的支持情况。清单 9. 了解 ROWID 类型的支持情况DatabaseMetaData meta = conn.getMetaData();
System.out.println(meta.getRowIdLifetime());Java SE 6 的 API 规范中,java.sql.RowIdLifetime 规定了 5 种不同的生命周期:ROWID_UNSUPPORTED、ROWID_VALID_FOREVER、ROWID_VALID_OTHER、ROWID_VALID_SESSION 和 ROWID_VALID_TRANSACTION。从字面上不难理解它们表示了不支持 ROWID、ROWID 永远有效等等。具体的信息,还可以参看相关的 JavaDoc。读者可以尝试着连接 Derby 进行试验,会发现运行结果是 ROWID_UNSUPPORTED ,即 Derby 并不支持 ROWID。既然提供了新的数据类型,那么一些相应的获取、更新数据表内容的新 API 也在 Java 6 中被添加进来。和其它已有的类型一样,在得到 ResultSet 或者 CallableStatement 之后,调用 get/set/update 方法得到/设置/更新 RowId 对象,示例的代码如
所示。清单 10. 获得/设置 RowId 对象// Initialize a PreparedStatement
PreparedStatement pstmt = connection.prepareStatement(
"SELECT rowid, name, score FROM hellotable WHERE rowid = ?");
// Bind rowid into prepared statement.
pstmt.setRowId(1, rowid);
// Execute the statement
ResultSet rset = pstmt.executeQuery();
// List the records
while(rs.next()) {
RowId id = rs.getRowId(1); // get the immutable rowid object
String name = rs.getString(2);
int score = rs.getInt(3);
}鉴于不同 DBMS 的不同实现,RowID 对象通常在不同的数据源(datasource)之间并不是可移植的。因此 JDBC 4.0 的 API 规范并不建议从连接 A 取出一个 RowID 对象,将它用在连接 B 中,以避免不同系统的差异而带来的难以解释的错误。而至于像 Derby 这样不支持 RowId 的 DBMS,程序将直接在 setRowId 方法处抛出 SQLFeatureNotSupportedException。SQLXMLSQL:2003 标准引入了 SQL/XML,作为 SQL 标准的扩展。SQL/XML 定义了 SQL 语言怎样和 XML 交互:如何创建 XML 数据;如何在 SQL 语句中嵌入 XQuery 表达式等等。作为 JDBC 4.0 的一部分,Java 6 增加了 java.sql.SQLXML 的类型。JDBC 应用程序可以利用该类型初始化、读取、存储 XML 数据。java.sql.Connection.createSQLXML 方法就可以创建一个空白的 SQLXML 对象。当获得这个对象之后,便可以利用 setString、setBinaryStream、setCharacterStream 或者 setResult 等方法来初始化所表示的 XML 数据。以 setCharacterStream 为例, 表示了一个 SQLXML 对象如何获取 java.io.Writer 对象,从外部的 XML 文件中逐行读取内容,从而完成初始化。清单 11. 利用 setCharacterStream 方法来初始化 SQLXML 对象SQLXML xml = con.createSQLXML();
Writer writer = xml.setCharacterStream();
BufferedReader reader = new BufferedReader(new FileReader("test.xml"));
String line=
while((line = reader.readLine() != null) {
writer.write(line);
}由于 SQLXML 对象有可能与各种外部的资源有联系,并且在一个事务中一直持有这些资源。为了防止应用程序耗尽资源,Java 6 提供了 free 方法来释放其资源。类似的设计在 java.sql.Array、Clob 中都有出现。至于如何使用 SQLXML 与数据库进行交互,其方法与其它的类型都十分相似。可以参照
中的例子在 Java SE 6 的 API 规范中找到 SQLXML 中对应的 get/set/update 方法构建类似的程序,此处不再赘述。SQLExcpetion 的增强在 Java SE 6 之前,有关 JDBC 的异常类型不超过 10 个。这似乎已经不足以描述日渐复杂的数据库异常情况。因此,Java SE 6 的设计人员对以 java.sql.SQLException 为根的异常体系作了大幅度的改进。首先,SQLException 新实现了 Iterable&Throwable& 接口。 实现了
程序的异常处理机制。这样简洁地遍历了每一个 SQLException 和它潜在的原因(cause)。清单 12. SQLException 的 for-each loop// Java 6 code
catch (Throwable e) {
if (e instanceof SQLException) {
for(Throwable ex : (SQLException)e ){
System.err.println(ex.toString());
}此外, 表示了全部的 SQLException 异常体系。除去原有的 SQLException 的子类,Java 6 中新增的异常类被分为 3 种:SQLReoverableException、SQLNonTransientException、SQLTransientException。在 SQLNonTransientException 和 SQLTransientException 之下还有若干子类,详细地区分了 JDBC 程序中可能出现的各种错误情况。大多数子类都会有对应的标准 SQLState 值,很好地将 SQL 标准和 Java 6 类库结合在一起。图 4. SQLException 异常体系在众多的异常类中,比较常见的有 SQLFeatureNotSupportedException,用来表示 JDBC 驱动不支持某项 JDBC 的特性。例如在 Derby 下运行
中的程序,就可以发现 Derby 的驱动并不支持 RowId 的特性。另外值得一提的是,SQLClientInfoException 直接继承自 SQLException,表示当一些客户端的属性不能被设置在一个数据库连接时所发生的异常。小结:更多新特性与展望在本文中,我们已经向读者介绍了 Java SE 6 中 JDBC 最重要的一些新特性:它们包括嵌在 JDK 中的 Java DB (Derby)和 JDBC 4.0 的一部分。当然,还有很多本文还没有覆盖到的新特性。比如增加了对 SQL 语言中 NCHAR、NVARCHAR、LONGNVARCHAR 和 NCLOB 类型的支持;在数据库连接池的环境下为管理 Statement 对象提供更多灵活、便利的方法等。此外,在 Java SE 6 的 beta 版中,曾经将 Annotation Query 的特性包含进来。这项特性定义了一系列 Query 和 DataSet 接口,程序员可以通过撰写一些 Annotation 来自定义查询并获得定制的数据集结果。但是,由于这一特性的参考实现最终不能满足 JDK 的质量需求,Sun 公司忍痛割爱,取消了在 Java SE 6 中发布其的计划。我们有理由相信,在以后的 JDK 版本中,这一特性以及更多新的功能将被包含进来,利用 Java 语言构建数据库的应用也会变得更为自然、顺畅。
相关主题阅读
文章的完整列表,了解 Java SE 6 其它重要的增强。:Java SE 6 的规范文档,可以找到绝大部分新特性的官方说明。参考 Sun 公司 Java SE 6 关于 JDBC 的 API 参考文档: 和 。
developerWorks :更多关于 Apache Derby 项目的技术文章和教程 。:关于 Java DB 的介绍。:Apache Derby 的快速入门手册。
添加或订阅评论,请先或。
有新评论时提醒我
static.content.url=http://www.ibm.com/developerworks/js/artrating/SITE_ID=10Zone=Java technology, Open sourceArticleID=252344ArticleTitle=Java SE 6 新特性: Java DB 和 JDBC 4.0publish-date=以下试题来自:
单项选择题在Java中使用JDBC时,对于多次调用同一条SQL语句的情况,使用()通常会提高效率。
A.Statement
B.CallableStatement
C.PrepareStatement
D.ParameterStatement
为您推荐的考试题库
您可能感兴趣的试卷
你可能感兴趣的试题
A.翻译期错误
B.编译JAVA源码时发生错误
C.执行编译后的字节码时发生错误
D.运行时,浏览器上显示:str is null
A.返回list集合,包含一条信息
B.返回list集合,包含所有数据信息
C.程序发生编译错误,无法运行
D.编译通过,运行时出错
A.session对象应用于整个应用程序
B.application对象存储有关用户的会话状况
C.request对象用于客户端的请求处理
D.out对象用于客户端输出数据
A.ArrayList
B.Connection
C.Statement
D.DriverManager
A.execute()
B.close()
C.executeUpdate()
D.executeQuery()java的事务处理,如果对数据库进行多次操作,每一次的执行或步骤都是一个事务.如果数据库操作在某一步没有执行或出现异常而导致事务失败,这样有的事务被执行有的就没有被执行,从而就有了事务的回滚,取消先前的操作.....
&&& 注:在Java中使用事务处理,首先要求数据库支持事务。如使用MySQL的事务功能,就要求MySQL的表类型为Innodb才支持事务。否则,在Java程序中做了commit或rollback,但在数据库中根本不能生效。
JavaBean中使用JDBC方式进行事务处理
public int delete(int sID) {
  dbc = new DataBaseConnection();
  Connection con = dbc.getConnection();
  try {
   con.setAutoCommit(false);// 更改JDBC事务的默认提交方式
   dbc.executeUpdate("delete from xiao where ID=" + sID);
   dbc.executeUpdate("delete from xiao_content where ID=" + sID);
   dbc.executeUpdate("delete from xiao_affix where bylawid=" + sID);
   con.commit();//提交JDBC事务
   con.setAutoCommit(true);// 恢复JDBC事务的默认提交方式
   dbc.close();
   return 1;
  }
  catch (Exception exc) {
   con.rollBack();//回滚JDBC事务
   exc.printStackTrace();
   dbc.close();
   return -1;
  }
}
&&&& 在数据库操作中,一项事务是指由一条或多条对数据库更新的sql语句所组成的一个不可分割的工作单元。只有当事务中的所有操作都正常完成了,整个事务才能被提交到数据库,如果有一项操作没有完成,就必须撤消整个事务。
例如在银行的转帐事务中,假定张三从自己的帐号上把1000元转到李四的帐号上,相关的sql语句如下:
update account set monery=monery-1000 where name='zhangsan'
update account set monery=monery+1000 where name='lisi'
这个两条语句必须作为一个完成的事务来处理。只有当两条都成功执行了,才能提交这个事务。如果有一句失败,整个事务必须撤消。
在connection类中提供了3个控制事务的方法:
(1) setAutoCommit(Boolean autoCommit):设置是否自动提交事务;
(2) commit();提交事务;
(3) rollback();撤消事务;
在jdbc api中,默认的情况为自动提交事务,也就是说,每一条对数据库的更新的sql语句代表一项事务,操作成功后,系统自动调用commit()来提交,否则将调用rollback()来撤消事务。
在jdbc api中,可以通过调用setAutoCommit(false) 来禁止自动提交事务。然后就可以把多条更新数据库的sql语句做为一个事务,在所有操作完成之后,调用commit()来进行整体提交。倘若其中一项 sql操作失败,就不会执行commit()方法,而是产生相应的sqlexception,此时就可以捕获异常代码块中调用rollback()方法撤消事务。
事务处理是企业应用需要解决的最主要的问题之一。J2EE通过JTA提供了完整的事务管理能力,包括多个事务性资源的管理能力。但是大部分应用都是运行在单一的事务性资源之上(一个数据库),他们并不需要全局性的事务服务。本地事务服务已然足够(比如JDBC事务管理)。
&&&& 本文并不讨论应该采用何种事务处理方式,主要目的是讨论如何更为优雅地设计事务服务。仅以JDBC事务处理为例。涉及到的DAO,Factory,Proxy,Decorator等模式概念,请阅读相关资料。
&&&& 也许你听说过,事务处理应该做在service层,也许你也正这样做,但是否知道为什么这样做?为什么不放在DAO层做事务处理。显而易见的原因是业务层 接口的每一个方法有时候都是一个业务用例(User Case),它需要调用不同的DAO对象来完成一个业务方法。比如简单地以网上书店购书最后的确定定单为例,业务方法首先是调用BookDAO对象(一般 是通过DAO工厂产生),BookDAO判断是否还有库存余量,取得该书的价格信息等,然后调用 CustomerDAO从帐户扣除相应的费用以及记录信息,然后是其他服务(通知管理员等)。简化业务流程大概如此:
&&&& 注意,我们的例子忽略了连接的处理,只要保证同一个线程内取的是相同的连接即可(可用ThreadLocal实现):
&&&& 首先是业务接口,针对接口,而不是针对类编程:
public interface BookStoreManager{
&&&&&&&&&& public boolean buyBook(String bookId,int quantity)throws SystemE
&&&&&&&&&& ....其他业务方法
&&&& }
&&&& 接下来就是业务接口的实现类??业务对象:
public class BookStoreManagerImpl implements BookStoreManager{
&&&&&&&&& public boolean buyBook(String bookId)throws SystemException{
&&&&&&&&&&&&&& Connection conn=ConnectionManager.getConnection();//获取数据库连接
&&&&&&&&&&&&&& boolean b=
&&&&&&&&&&&&&
&&&&&&&&&&&&&& try{
&&&&&&&&&&&&&&&&&& conn.setAutoCommit(false);&& //取消自动提交
&&&&&&&&&&&&&&&&&& BookDAO bookDAO=DAOFactory.getBookDAO();
&&&&&&&&&&&&&&&&&& CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
&&&&&&&&&&&&&&&&&&&& //尝试从库存中取书
&&&&&&&&&&&&&&&&&& if(BookDAO.reduceInventory(conn,bookId,quantity)){
&&&&&&&&&&&&&&&&&&&&&&& BigDecimal price=BookDAO.getPrice(bookId);&& //取价格
&&&&&&&&&&&&&&&&&&&&&&& //从客户帐户中扣除price*quantity的费用
&&&&&&&&&&&&&&&&&&&&&&& b=
&&&&&&&&&&&&&&&&&&&&&&& CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity));
&&&&&&&&&&&&&&&&&&&&&&& ....
&&&&&&&&&&&&&&&&&&&&&&& 其他业务方法,如通知管理员,生成定单等.
&&&&&&&&&&&&&&&&&&&&&&&& ...
&&&&&&&&&&&&&&&&&&&&&&& conn.commit();&&& //提交事务
&&&&&&&&&&&&&&&&&&&&&&& conn.setAutoCommit(true);
&&&&&&&&&&&&&&&&&& }
&&&&&&&&&&&&&&& }catch(SQLException e){
&&&&&&&&&&&&&&&&&& conn.rollback();&&& //出现异常,回滚事务
&&&&&&&&&&&&&&&&&& con.setAutoCommit(true);
&&&&&&&&&&&&&&&&&& e.printStackTrace();
&&&&&&&&&&&&&&&&&& throws new SystemException(e);&&
&&&&&&&&&&&&&&& }
&&&&&&&&&&&&&&&
&&&&&&&&& }
&&&& }
&&&& 然后是业务代表工厂:&
&& public final class ManagerFactory {
&&&&&& public static BookStoreManager getBookStoreManager() {
&&&&&&&&& return new BookStoreManagerImpl();
&&&&&& }
&&& }
&&&& 这样的设计非常适合于DAO中的简单活动,我们项目中的一个小系统也是采用这样的设计方案,但是它不适合于更大规模的应用。首先,你有没有闻到代码重复的 bad smell?每次都要设置AutoCommit为false,然后提交,出现异常回滚,包装异常抛到上层,写多了不烦才怪,那能不能消除呢?其次,业务代 表对象现在知道它内部事务管理的所有的细节,这与我们设计业务代表对象的初衷不符。对于业务代表对象来说,了解一个与事务有关的业务约束是相当恰当的,但 是让它负责来实现它们就不太恰当了。再次,你是否想过嵌套业务对象的场景?业务代表对象之间的互相调用,层层嵌套,此时你又如何处理呢?你要知道按我们现 在的方式,每个业务方法都处于各自独立的事务上下文当中(Transaction Context),互相调用形成了嵌套事务,此时你又该如何处理?也许办法就是重新写一遍,把不同的业务方法集中成一个巨无霸包装在一个事务上下文中。
&&&& 我们有更为优雅的设计来解决这类问题,如果我们把Transaction Context的控制交给一个被业务代表对象、DAO和其他Component所共知的外部对象。当业务代表对象的某个方法需要事务管理时,它提示此外部 对象它希望开始一个事务,外部对象获取一个连接并且开始数据库事务。也就是将事务控制从service层抽离,当 web层调用service层的某个业务代表对象时,返回的是一个经过Transaction Context外部对象包装(或者说代理)的业务对象。此代理对象将请求发送给原始业务代表对象,但是对其中的业务方法进行事务控制。那么,我们如何实现 此效果呢?答案是JDK1.3引进的动态代理技术。动态代理技术只能代理接口,这也是为什么我们需要业务接口BookStoreManager的原因。
&&&& 首先,我们引入这个Transaction Context外部对象,它的代码其实很简单,如果不了解动态代理技术的请先阅读其他资料。
import java.lang.reflect.InvocationH
import java.lang.reflect.M
import java.lang.reflect.P
import java.sql.C
import com.strutslet.demo.service.SystemE
public final class TransactionWrapper {
&&&& /**
&&&&& * 装饰原始的业务代表对象,返回一个与业务代表对象有相同接口的代理对象
&&&&& */
&&&& public static Object decorate(Object delegate) {
&&&&&&&& return Proxy.newProxyInstance(delegate.getClass().getClassLoader(),
&&&&&&&&&&&&&&&& delegate.getClass().getInterfaces(), new XAWrapperHandler(
&&&&&&&&&&&&&&&&&&&&&&&& delegate));
&&&& }
&&&
&&&& //动态代理技术
&&&& static final class XAWrapperHandler implements InvocationHandler {
&&&&&&&& private final O
&&&&&&&& XAWrapperHandler(Object delegate) {
&&&&&&&&&&& this.delegate =
&&&&&&&& }
&&&&&&&
&&&&&&&& //简单起见,包装业务代表对象所有的业务方法
&&&&&&&& public Object invoke(Object proxy, Method method, Object[] args)
&&&&&&&&&&&&&&&& throws Throwable {
&&&&&&&&&&&& Object result =
&&&&&&&&&&&& Connection con = ConnectionManager.getConnection();
&&&&&&&&&&&& try {
&&&&&&&&&&&&&&&& //开始一个事务
&&&&&&&&&&&&&&&& con.setAutoCommit(false);
&&&&&&&&&&&&&&&& //调用原始业务对象的业务方法
&&&&&&&&&&&&&&&& result = method.invoke(delegate, args);
&&&&&&&&&&&&&&&& con.commit();&&& //提交事务
&&&&&&&&&&&&&&&& con.setAutoCommit(true);
&&&&&&&&&&&& } catch (Throwable t) {
&&&&&&&&&&&&&&&& //回滚
&&&&&&&&&&&&&&&& con.rollback();
&&&&&&&&&&&&&&&& con.setAutoCommit(true);
&&&&&&&&&&&&&&&& throw new SystemException(t);
&&&&&&&&&&&& }
&&&&&&&&&&&&
&&&&&&&& }
&&&& }
}
&&&& 正如我们所见,此对象只不过把业务对象需要事务控制的业务方法中的事务控制部分抽取出来而已。请注意,业务代表对象内部调用自身的方法将不会开始新的事务,因为这些调用不会传给代理对象。如此,我们去除了代表重复的味道。此时,我们的业务代表对象修改成:
public class BookStoreManagerImpl implements BookStoreManager {
&&&& public boolean buyBook(String bookId)throws SystemException{
&&&&&&&&&& Connection conn=ConnectionManager.getConnection();// 获取数据库连接
&&&&&&&&&& boolean b=
&&&&&&&&&& try{
&&&&&&&&&&&&&& BookDAO bookDAO=DAOFactory.getBookDAO();
&&&&&&&&&&&&&& CustomerDAO customerDAO=DAOFactory.getCustomerDAO();
&&&&&&&&&&&&&& // 尝试从库存中取书
&&&&&&&&&&&&&& if(BookDAO.reduceInventory(conn,bookId,quantity)){
&&&&&&&&&&&&&&&&&& BigDecimal price=BookDAO.getPrice(bookId);&& // 取价格
&&&&&&&&&&&&&&&&&& // 从客户帐户中扣除price*quantity的费用
&&&&&&&&&&&&&&&&&& b=
&&&&&&&&&&&&&&&&&& CustomerDAO.reduceAccount(conn,price.multiply(new BigDecimal(quantity));
&&&&&&&&&&&&&&&&&& ....
&&&&&&&&&&&&&&&&&& 其他业务方法,如通知管理员,生成定单等.
&&&&&&&&&&&&&&&&&& ...
&&&&&&&&&&&&&& }
&&&&&&&&&& }catch(SQLException e){
&&&&&&&&&&&&& throws new SystemException(e);
&&&&&&&&&& }
&&&&&&&&&&
&&&& }
&&&& ....
}
&&&& 可以看到,此时的业务代表对象专注于实现业务逻辑,它不再关心事务控制细节,把它们全部委托给了外部对象。业务代表工厂也修改一下,让它返回两种类型的业务代表对象:
public final class ManagerFactory {
&&&&&& //返回一个被包装的对象,有事务控制能力
&&&&&& public static BookStoreManager getBookStoreManagerTrans() {
&&&&&&&&&& return (BookStoreManager) TransactionWrapper
&&&&&&&&&&&&&&&&&& .decorate(new BookStoreManagerImpl());
&&&&&& }
&&&&&& //原始版本
&&&&&& public static BookStoreManager getBookStoreManager() {
&&&&&&&&& return new BookStoreManagerImpl();
&&&&&& }
&&&&&& ......
&&& }
&&&& 我们在业务代表工厂上提供了两种不同的对象生成方法:一个用于创建被包装的对象,它会为每次方法调用创建一个新的事务;另外一个用于创建未被包装的版本,它用于加入到已有的事务(比如其他业务代表对象的业务方法),解决了嵌套业务代表对象的问题。
&&& 我们的设计还不够优雅,比如我们默认所有的业务代表对象的方法调用都将被包装在一个Transaction Context。可事实是很多方法也许并不需要与数据库打交道,如果我们能配置哪些方法需要事务声明,哪些不需要事务管理就更完美了。解决办法也很简单, 一个XML配置文件来配置这些,调用时判断即可。说到这里,了解spring的大概都会意识到这不正是声明式事务控制吗?正是如此,事务控制就是AOP的 一种服务,spring的声明式事务管理是通过AOP实现的。AOP的实现方式包括:动态代理技术,字节码生成技术(如CGLIB库),java代码生成 (早期EJB采用),修改类装载器以及源代码级别的代码混合织入(aspectj)等。我们这里就是利用了动态代理技术,只能对接口代理;对类的动态代理 可以使用cglib库
简单事务的概念
我不想从原理上说明什么是事务,应为那太枯燥了。我只想从一个简单的例子来说明什么是事务。
  例如我们有一个订单库存管理系统,每一次生成订单的同时我们都要消减库存。通常来说订单和库存在数据库里是分两张表来保存的:订单表,库存表。每一次我们追加一个订单实际上需要两步操作:在订单表中插入一条数据,同时修改库存的数据。
  这样问题来了,例如我们需要一个单位为10的订单,库存中有30件,理想的操作是我们在订单表中插入了一条单位为10的订单,之后将库存表中的数据修 改为20。但是有些时候事情并不是总是按照你的想法发生,例如:在你修改库存的时候,数据库突然由于莫名其妙的原因无法连接上了。也就是说库存更新失败了。但是订单已经产生了,那么怎么办呢?没办法,只有手动的修改。所以最好的方式是将订单插入的操作和库存修改的操作绑定在一起,必须同时成功或者什么都 不做。这就是事务。
  Java如何处理事务呢?
  我们从java.sql.Connection说起,Connection表示了一个和数据库的链接,可以通过Connection来对数据库操作。 在通常情况是Connection的属性是自动提交的,也就是说每次的操作真的更新了数据库,真的无法回退了。针对上述的例子,一旦库存更新失败了,订单无法回退,因为订单真的插入到了数据库中。这并不是我们希望的。
  我们希望的是:看起来成功了,但是没有真的操作数据库,知道我想让他真的发生。可以通过Connection的setAutoCommit (false)让Connection不自动提交你的数据,除非你真的想提交。那么如何让操作真的发生呢?可以使用Connection的commit方 法。如何让操作回退呢?使用rollback方法。
Connection conn = getConnection(); // 不管如何我们得到了链接
conn.setAutoCommit(false);
// 插入订单
// 修改库存
conn.commit(); // 成功的情况下,提交更新。
} catch(SQLException ex) {
conn.rollback(); // 失败的情况下,回滚所有的操作
} finally {
conn.close();
  这里有一点非常重要,事务是基于数据库链接的。所以在但数据库的情况下,事务操作很简单。
  那么如果表分布在两个不同的数据库中呢?
  例如订单表在订单库中,库存表在库存库中,那么我们如何处理这样的事务呢?
  需要注意,提交也可以遇到错误呀!
Connection conn1 = getConnection1();
Connection conn2 = getConnection2();
// 基于conn1做插入操作
// 基于conn2做更新操作
conn1.commit()
} catch(SQLExcetion ) {
conn1.rollback();
conn2.commit();
} catch(SQLException ) {
conn2.rollbakc();
// 保证肯定删除刚才插入的订单。
} catch(SQLException ex) {
// 如果插入失败,conn1.rollback
// 如果更新失败,conn1.rollback && conn2.rollback
} finally {
conn1.close();
conn2.close();
  看看上述的代码就知道,其实操作非常的复杂,甚至:保证肯定删除刚才插入的订单根本无法保证。
  在上述情况下的事务可以称之为分布式事务,通过上述的代码中事务同时提交处理的部分我们可以得出,要想处理分布式事务,必须有独立于数据库的第三方的事务处理组件。
  幸运的是通常情况下,JavaEE兼容的应用服务器,例如:Weblogic,Websphere,JBoss,Glassfish等都有这种分布式事务处理的组件。
  如何使用应用服务器的分布式事务管理器处理分布式事务?
  以galssfish为例
  1 建立对应两个数据库的XA(javax.sql.XADataSource)类型的数据源。
  2 使用UserTransaction来保证分布式事务。
Connection conn1 = datasource1.getConnection();
Connection conn2 = datasource2.getConnection();
UserTransaction ut = getUserTransaction();
ut.begin();
// 插入订单
// 修改库存
ut.commit(); // 成功的情况下,提交更新。
} catch(SQLException ex) {
ut.rollback(); // 失败的情况下,回滚所有的操作
} finally {
conn.close();
  如何获取UserTransaction呢?可以使用如下方法
UserTransaction tx = (UserTransaction)
ctx.lookup("jndi/UserTransaction");
J2EE开发人员使用数据访问对象(DAO)设计模式把底层的数据访问逻辑和高层的商务逻辑分开。实现DAO模式能够更加专注于编写数据访问代码。这篇文章中,Java开发人员Sean C. Sullivan从三个方面讨论DAO编程的结构特征:事务划分,异常处理,日志记录。
  在最近的18个月,我和一个优秀的软件开发团队一起工作,开发定制基于WEB的供应链管理应用程序.我们的应用程序访问广泛的持久层数据,包括出货状态,供应链制度,库存,货物发运,项目管理数据,和用户属性等.我们使用JDBC API连接我们公司的各种数据库平台,并且在整个应用程序中应用了DAO设计模式.
  通过在整个应用程序中应用数据访问对象(DAO)设计模式使我们能够把底层的数据访问逻辑和上层的商务逻辑分开.我们为每个数据源创建了提供CRUD(创建,读取,更新,删除)操作的DAO类.
  在本文中,我将向你介绍DAO的实现策略以及创建更好的DAO类的技术.我会明确的介绍日志记录,异常处理,和事务划分三项技术.你将学在你的DAO类中怎样把这三种技术结合在一起.这篇文章假设你熟悉JDBC API,SQL和关系性数据库编程.
  我们先来回顾一下DAO设计模式和数据访问对象.
  DAO基础
  DAO模式是标准的J2EE设计模式之一.开发人员使用这个模式把底层的数据访问操作和上层的商务逻辑分开.一个典型的DAO实现有下列几个组件:
  1. 一个DAO工厂类;
  2. 一个DAO接口;
  3. 一个实现DAO接口的具体类;
  4. 数据传递对象(有些时候叫做值对象).
  具体的DAO类包含了从特定的数据源访问数据的逻辑。在下面的这段中你将学到设计和实现数据访问对象的技术。
  事务划分:
  关于DAO要记住的一件重要事情是它们是事务性对象。每个被DAO执行的操作(象创建,更新、或删除数据)都是和事务相关联的。同样的,事务划分(transaction demarcation)的概念是特别重要的。
  事务划分是在事务界定定义中的方式。J2EE规范为事务划分描述了两种模式:编程性事务(programmatic)和声明性事务(declarative)。下表是对这两种模式的拆分:
声明性事务划分
编程性事务划分
程序员使用EJB的布署描述符声明事务属性
程序员担负编写事务逻辑代码的责任。
运行时环境(EJB容器)使用这些属性来自动的管理事务。
应用程序通过一个API接口来控制事务。
  我将把注意力集中的编程性事务划分上。
  象前面的介绍一样,DAOs是一些事务对象。一个典型的DAO要执行象创建、更新、和删除这的事务性操作。在设计一个DAO时,首先要问自己如下问题:
  1、 事务将怎样开始?
  2、 事务将怎样结束?
  3、 那个对象将承担起动一个事务的责任?
  4、 那个对象将承担结束一个事务的责任?
  5、 DAO应该承担起动和结束事务的责任?
  6、 应用程序需要交叉访问多个DAO吗?
  7、 一个事务包含一个DAO还是多个DAO?
  8、 一个DAO包含其它的DAO中的方法吗?
  回答这些问题将有助于你为DAO对象选择最好的事务划分策略。对ADO中的事务划分有两个主要的策略。一种方法是使用DAO承担事务划分的责任;另一种是延期性事务,它把事务划分到调用DAO对象的方法中。如果你选择前者,你将要在DAO类中嵌入事务代码。如果你选择后者,事务代码将被写在DAO类的 外部。我们将使用简单的代码实例来更好的理解这两种方法是怎样工作的。
  实例1展示了一个带有两种数据操作的DAO:创建(create)和更新(update):
public void createWarehouseProfile(WHProfile profile);
public void updateWarehouseStatus(WHIdentifier id, StatusInfo status);
  实例2展示了一个简单的事务,事务划分代码是在DAO类的外部。注意:在这个例子中的调用者把多个DOA操作组合到这个事务中。
tx.begin(); // start the transaction
dao.createWarehouseProfile(profile);
dao.updateWarehouseStatus(id1, status1);
dao.updateWarehouseStatus(id2, status2);
tx.commit(); // end the transaction
  这种事务事务划分策略对在一个单一事务中访问多个DAO的应用程序来说尤为重要。
  你即可使用JDBC API也可以使用Java 事务API(JTA)来实现事务的划分。JDBC事务划分比JTA事务划分简单,但是JTA提供了更好的灵活性。在下面的这段中,我们会进一步的看事务划分机制。
  使用JDBC的事务划分
  JDBC事务是使用Connection对象来控制的。JDBC的连接接口(java.sql.Connection)提供了两种事务模式:自动提交和手动提交。Java.sql.Connection为控制事务提供了下列方法:
.public void setAutoCommit(Boolean)
.public Boolean getAutoCommit()
.public void commit()
.public void rollback()
  实例3展示怎样使用JDBC API来划分事务:
import java.sql.*;
import javax.sql.*;
// ...
DataSource ds = obtainDataSource();
Connection conn = ds.getConnection();
conn.setAutoCommit(false);
// ...
pstmt = conn.prepareStatement(";UPDATE MOVIES ...";);
pstmt.setString(1, ";The Great Escape";);
pstmt.executeUpdate();
// ...
conn.commit();
// ...
  使用JDBC事务划分,你能够把多个SQL语句组合到一个单一事务中。JDBC事务的缺点之一就是事务范围被限定在一个单一的数据库连接中。一个 JDBC事务不能够跨越多个数据库。接下来,我们会看到怎样使用JTA来做事务划分的。因为JTA不象JDBC那样被广泛的了解,所以我首先概要的介绍一 下JTA。
  JTA概要介绍
  Java事务API(JTA;Java Transaction API)和它的同胞Java事务服务(JTS;Java Transaction Service),为J2EE平台提供了分布式事务服务。一个分布式事务(distributed transaction)包括一个事务管理器(transaction manager)和一个或多个资源管理器(resource manager)。一个资源管理器(resource manager)是任意类型的持久化数据存储。事务管理器(transaction manager)承担着所有事务参与单元者的相互通讯的责任。下车站显示了事务管理器和资源管理的间的关系。
  JTA事务比JDBC事务更强大。一个JTA事务可以有多个参与者,而一个JDBC事务则被限定在一个单一的数据库连接。下列任一个Java平台的组件都可以参与到一个JTA事务中:
  .JDBC连接
  .JDO PersistenceManager 对象
  .JMS 队列
  .JMS 主题
  .企业JavaBeans(EJB)
  .一个用J2EE Connector Architecture 规范编译的资源分配器。
  使用JTA的事务划分
  要用JTA来划分一个事务,应用程序调用javax.transaction.UserTransaction接口中的方法。示例4显示了一个典型的JNDI搜索的UseTransaction对象。
import javax.transaction.*;
import javax.naming.*;
// ...
InitialContext ctx = new InitialContext();
Object txObj = ctx.lookup(";java:comp/UserTransaction";);
UserTransaction utx = (UserTransaction) txObj;
  应用程序有了UserTransaction对象的引用之后,就可以象示例5那样来起动事务。
utx.begin();
// ...
DataSource ds = obtainXADataSource();
Connection conn = ds.getConnection();
pstmt = conn.prepareStatement(";UPDATE MOVIES ...";);
pstmt.setString(1, ";Spinal Tap";);
pstmt.executeUpdate();
// ...
utx.commit();
// ...
  当应用程序调用commit()时,事务管理器使用两段提交协议来结束事务。JTA事务控制的方法:
  .javax.transaction.UserTransaction接口提供了下列事务控制方法:
.public void begin()
.public void commit()
.public void rollback()
.public void getStatus()
.public void setRollbackOnly()
.public void setTransactionTimeout(int)
  应用程序调用begin()来起动事务,即可调用commit()也可以调用rollback()来结束事务。
  使用JTA和JDBC
  开发人员经常使用JDBC来作为DAO类中的底层数据操作。如果计划使用JTA来划分事务,你将需要一个实现了javax.sql.XADataSource,javax.sql.XAConnection和javax.sql.XAResource接口JDBC的驱动。实现了这些接口的驱动将有能力参与到JTA事务中。一个XADataSource对象是一个XAConnection对象的工厂。XAConnections是参与到JTA事务中的连接。
  你需要使用应用程序服务器管理工具来建立XADataSource对象。对于特殊的指令请参考应用程序服务器文档和JDBC驱动文档。
  J2EE应用程序使用JNDI来查找数据源。一旦应用程序有了一个数据源对象的引用,这会调用javax.sql.DataSource.getConnection()来获得数据库的连接。
  XA连接区别于非XA连接。要记住的是XA连接是一个JTA事务中的参与者。这就意味着XA连接不支持JDBC的自动提交特性。也就是说应用程序不必 在XA连接上调用java.sql.Connection.commit()或java.sql.Connection.rollback()。相反,应 用程序应该使用UserTransaction.begin()、UserTransaction.commit()和 UserTransaction.rollback().
  选择最好的方法
  我们已经讨论了JDBC和JTA是怎样划分事务的。每一种方法都有它的优点,回此你需要决定为你的应用程序选择一个最适应的方法。在我们团队许多最近的对于事务划分的项目中使用JDBC API来创建DAO类。这DAO类总结如下:
  .事务划分代码被嵌入到DAO类内部
  .DAO类使用JDBC API来进行事务划分
  .调用者没有划分事务的方法
  .事务范围被限定在一个单一的JDBC连接
  JDBC事务对复杂的企业应用程序不总是有效的。如果你的事务将跨越多个DAO对象或多个数据库,那么下面的实现策略可能会更恰当:
  .用JTA对事务进行划分
  .事务划分代码被DAO分开
  .调用者承担划分事务的责任
  .DAO参与一个全局的事务中
  JDBC方法由于它的简易性而具有吸引力,JTA方法提供了更多灵活性。你选择什么样的实现将依赖于你的应用程序的特定需求。
  日志记录和DAO
  一个好的DAO实现类将使用日志记录来捕获有关它在运行时的行为细节。你可以选择记录异常、配置信息、连接状态、JDBC驱动程序的元数据或查询参数。日志对开发整个阶段都是有益的。我经常检查应用程序在开发期间、测试期间和产品中的日志记录。
  在这段中,我们将展现一段如何把Jakarta Commaons Logging结合中一个DAO中的例子。在我们开始之前,让我们先回顾一些基础知识。
  选择一个日志例库
  许多开发人员使用的基本日志形式是:System.out.println和System.err.println.Println语句。这种形式快捷方便,但它们不能提供一个完整的日志系统的的能力。下表列出了Java平台的日志类库:
Java.util.logging
http://java.sun.com/j2ee
Jakarta Log4j
http://hajarta.apache.org/log4j/
Jakarta Commons Logging
http:/Jakarta.apache.org/commons/logging.html
  Java.util.logging是J2SE1.4平台上的标准的API。但是,大多数开发人员都认为Jakarta Log4j提供了更大的功能性和灵活性。Log4j超越java.util.logging的优点之一就是它支持J2SE1.3和J2SE1.4平台。
  Jakarta Commons Logging能够被用于和java.util.loggin或Jakarta Log4j一起工作。Commons Logging是一个把你的应用程序独立于日志实现的提取层。使用Commons Logging你能够通过改变一个配置文件来与下面的日志实现来交换数据。Commons Logging被用于JAKARTA Struts1.1和Jakarta HttpClient2.0中。
  一个日志示例
  示例7显示了在一个DOA类中怎样使用Jakarta Commons Logging
import org.apache.commons.logging.*;
class DocumentDAOImpl implements DocumentDAO
{
static private final Log log = LogFactory.getLog(DocumentDAOImpl.class);
public void deleteDocument(String id)
{
// ...
log.debug(";deleting document: "; + id);
// ...
try
{
// ... data operations ...
}
catch (SomeException ex)
{
log.error(";Unable to delete document"; ex);
// ... handle the exception ...
}
}
}
  日志是评估应用程序的基本部分。如果你在一个DAO中遇到了失败,日志经常会为理解发生的什么错误提供最好的信息。把日志结合到你的DAO中,确保得到调试和解决问题的有效手段。
  DAO中的异常处理
  我们已经看了事务划分和日志记录,并且现在对于它们是怎样应用于数据访问对象的有一个深入的理解。我们第三部分也是最后要讨论的是异常处理。下面的一些简单的异常处理方针使用你的DAO更容易使用,更加健壮和更具有可维护性。
  在实现DAO模式的时候,要考滤下面的问题:
  .在DAO的public接口中的方法将抛出被检查的异常吗?
  .如果是,将抛出什么样的检查性异常?
  .在DAO实现类中怎能样处理异常。
  在用DAO模式工作的过程中,我们的团队为异常处理开发了一组方针。下面的这些方针会很大程度的改善你的DAO:
  .DAO方法应该抛出有意义的异常。
  .DAO方法不应该抛出java.lang.Exception异常。因为java.lang.Exception太一般化,它不能包含有关潜在问题的所有信息。
  .DAO方法不应该抛出java.sql.SQLException异常。SQLException是一个底层的JDBC异常,DAO应用努力封装JDBC异常而不应该把JDBC异常留给应用程序的其它部分。
  .在DAO接口中的方法应该只抛出调用者期望处理的检查性异常。如果调用者不能用适当的方法来处理异常,考滤抛出不检查性(运行时run-time)异常。
  .如果你的数据访问代码捕获了一个异常,不可要忽略它。忽略捕获异常的DAO是很处理的。
  .使用异常链把底层的异常传递给高层的某个处理器。
  .考滤定义一个标准的DAO异常类。Spring框架提供了一个优秀的预定义的DAO异常类的集合。
  看Resources,查看有异常和异常处理技术的更详细信息。
  实现示例:MovieDAO
  MoveDAO是一个示范了在这篇文章中所讨论的所有技术,包括事务划分、日志记录和异常处理。你会在Resources段找到MovieDAO的源代码。它被分下面的三个包:
.daoexamples.exception
.daoexamples.move
.daoexamples.moviedemo
  这个DAO模式的实现由下面的类和接口组成:
.daoexamples.movie.MovieDAOFactory
.daoexamples.movie.MovieDAO
.daoexamples.movie.MovieDAOImpl
.daoexamples.movie.MovieDAOImplJTA
.daoexamples.movie.Movie
.daoexamples.movie.MovieImple
.daoexamples.movie.MovieNotFoundException
.daoexamples.movie.MovieUtil
  MovieDAO接口定义了DAO的数据操作。这个接口有如下五个方法:
.public Movie findMovieById(String id)
.public java.util.Collection findMoviesByYear(String year)
.public void deleteMovie(String id)
.public Movie createMovie(String rating,String year,String title)
.public void updateMovie(String id,String rating,String year,String title)
  daoexamples.movie包包含了两个MovieDAO接口的实现。每个实现使用了一个同的事务划分方法,如下表所示:
MovieDAOImpl
MovieDAOImplJTA
实现了MovieDAO接口吗?
通过JNDI获得DataSource吗?
从一个DataSource获得java.sql.Connection对象吗?
DAO界定内部的事务吗?
使用JDBC事务吗?
使用一个XA DataSource吗?
分担JTA事务吗?
  MovieDAO 示范应用程序
  这个示范应用程序是一个叫做daoexamples.moviedemo.DemoServlet.DemoServlet的servlet类,它使用Movie DAO来查询和更新一个表中的movie数据。
  这个servlet示范了把JTA感知的MovieDAO和Java消息服务组合到一个单一的事务中,如示例8所示:
UserTransaction utx = MovieUtil.getUserTransaction();
utx.begin();
batman = dao.createMovie(";R";
";2008";
";Batman Reloaded";);
publisher = new MessagePublisher();
publisher.publishTextMessage(";I’ll be back";);
dao.updateMovie(topgun.getId(),
";PG-13";
topgun.getReleaseYear(),
topgun.getTitle());
dao.deleteMovie(legallyblonde.getId());
utx.commit();
  要运行这个范例应用程序,在你的应用程序服务器中配置一个XA 数据源和一个非XA数据源。然后布署daoexamples.ear文件。这个应用程序将运行在任何与J2EE兼容的应用程序服务器。
  信息是任何企事业单位的重要资产,任何企业部门都包含着信息的流入、流出,任何企业部门都控制着某些信息。同时,信息必须在适当的时机传播给需要的 人。而且,信息还需要安全约束,通常根据信息的类型和内容实施访问控制。为了保证数据的安全有效和正确可靠,数据库管理系统(DBMS)必须提供统一的数 据保护功能。
  事务是现代数据库理论中的核心概念之一。如果一组处理步骤或者全部发生或者一步也不执行,我们称该组处理步骤为一个事务。当所有的步骤像一个操作一样被完整地执行,我们称该事务被提交。由于其中的一部分或多步执行失败,导致没有步骤被提交,则事务必须回滚(回到最初的系统状态)。事务必须服从 ISO/IEC所制定的ACID原则。ACID是原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久 性(durability)的缩写。事务的原子性表示事务执行过程中的任何失败都将导致事务所做的任何修改失效。一致性表示当事务执行失败时,所有被该事务影响的数据都应该恢复到事务执行前的状态。隔离性表示在事务执行过程中对数据的修改,在事务提交之前对其他事务不可见。持久性表示已提交的数据在事务执 行失败时,数据的状态都应该正确。
  在下面我们列举一个使用SQL Server数据库进行事务处理的例子。主表是一个规章制度信息表(bylaw),主要字段有记录编号、标题、作者、书写日期等。两个子表分别是附件表 (bylaw_affix)和文本信息表(bylaw_content)。表结构见图1所示。bylaw表的记录编号与bylaw_affix表的记录编 号、bylaw_content表的记录编号是对应的,每次对规章制度信息的操作也就是对这三个表的联合操作。例如要删除规章制度中的一条记录,如果不使用事务,就可能会出现这样的情况:第一个表中成功删除后,数据库突然出现意外状况,而第二、三个表中的操作没有完成,这样,删除操作并没有完成,甚至已经 破坏数据库中的数据。要避免这种情况,就应该使用事务,它的作用是:要么三个表都操作成功,要么都失败。换句话说,就是保持数据的一致性。所以,为了确保对数据操作的完整和一致,在程序设计时要充分考虑到事务处理方面的问题。
图1 示例表结构
  Java中的事务处理
  一般情况下,J2EE应用服务器支持JDBC事务、JTA(Java Transaction API)事务、容器管理事务。一般情况下,最好不要在程序中同时使用上述三种事务类型,比如在JTA事务中嵌套JDBC事务。第二方面,事务要在尽可能短 的时间内完成,不要在不同方法中实现事务的使用。下面我们列举两种事务处理方式。
1、JavaBean中使用JDBC方式进行事务处理
在JDBC中怎样将多个SQL语句组合成一个事务呢?在JDBC中,打开一个连接对象Connection时,缺省是auto-commit模式,每个 SQL语句都被当作一个事务,即每次执行一个语句,都会自动的得到事务确认。为了能将多个SQL语句组合成一个事务,要将auto-commit模式屏蔽 掉。在auto-commit模式屏蔽掉之后,如果不调用commit()方法,SQL语句不会得到事务确认。在最近一次commit()方法调用之后的 所有SQL会在方法commit()调用时得到确认。
public int delete(int sID) {
dbc = new DataBaseConnection();
Connection con = dbc.getConnection();
try {
con.setAutoCommit(false);// 更改JDBC事务的默认提交方式
dbc.executeUpdate("delete from bylaw where ID=" + sID);
dbc.executeUpdate("delete from bylaw _content where ID=" + sID);
dbc.executeUpdate("delete from bylaw _affix where bylawid=" + sID);
con.commit();//提交JDBC事务
con.setAutoCommit(true);// 恢复JDBC事务的默认提交方式
dbc.close();
return 1;
}
catch (Exception exc) {
con.rollBack();//回滚JDBC事务
exc.printStackTrace();
dbc.close();
return -1;
}
}
2、SessionBean中的JTA事务
JTA 是事务服务的 J2EE 解决方案。本质上,它是描述事务接口(比如 UserTransaction 接口,开发人员直接使用该接口或者通过 J2EE 容器使用该接口来确保业务逻辑能够可靠地运行)的 J2EE 模型的一部分。JTA 具有的三个主要的接口分别是 UserTransaction 接口、TransactionManager 接口和 Transaction 接口。这些接口共享公共的事务操作,例如 commit() 和 rollback(), 但是也包含特殊的事务操作,例如 suspend(),resume() 和 enlist(),它们只出现在特定的接口上,以便在实现中允许一定程度的访问控制。例如,UserTransaction 能够执行事务划分和基本的事务操作,而 TransactionManager 能够执行上下文管理。
应用程序可以调用UserTransaction.begin()方法开始一个事务,该事务与应用程序正在其中运行的当前线程相关联。底层的事务管理器实际处理线程与事务之间的关联。UserTransaction.commit()方法终止与当前线程关联的事务。 UserTransaction.rollback()方法将放弃与当前线程关联的当前事务。
public int delete(int sID) {
DataBaseConnection dbc =
dbc = new DataBaseConnection();
dbc.getConnection();
UserTransaction transaction = sessionContext.getUserTransaction();//获得JTA事务
try {
transaction.begin(); //开始JTA事务
dbc.executeUpdate("delete from bylaw where ID=" + sID);
dbc.executeUpdate("delete from bylaw _content where ID=" + sID);
dbc.executeUpdate("delete from bylaw _affix where bylawid=" + sID);
transaction.commit(); //提交JTA事务
dbc.close();
return 1;
}
catch (Exception exc) {
try {
transaction.rollback();//JTA事务回滚
}
catch (Exception ex) {
//JTA事务回滚出错处理
ex.printStackTrace();
}
exc.printStackTrace();
dbc.close();
return -1;
}
}
Can't start a cloned connection while in manual transaction mode错误 20:30出现Can't start a cloned connection while in manual transaction mode错误,从网上找到原因及解决办法如下:
原因一般是当你在一个SQL SERVER的JDBC连接上执行多个STATEMENTS的操作,或者是手动事务状态(AutoCommit=false) 并且使用默认的模式. direct (SelectMethod=direct) 模式.
解决办法
当你使用手动事务模式时,必须把SelectMethod 属性的值设置为 Cursor, 或者是确保在你的连接只有一个STATEMENT操作。
加入SelectMethod=cursor即可
如:jdbc:microsoft:sqlserver://localhost:1433;DatabaseName=SelectMethod=CUser=Password=ys");
package _
import java.sql.*;
import java.util.StringT
public class connDB{
&&& String sDBDriver = "com.microsoft.jdbc.sqlserver.SQLServerDriver";
&&& String sConnStr = "jdbc:microsoft:sqlserver://127.0.0.1:1433;SelectMethod=DatabaseName=myDB;user=password=river";
&&& Connection cn =
&&& S
&&& boolean autoC
&&& private String DbType="MYSQL";
&&& //private String DbType="Oracle";
&&& private connDB(){
&&&&&& init();
&&& }
&&& private void init(){
&&&&&&& try{
&&&&&&&&&&& Class.forName(sDBDriver).newInstance();
&&&&&&&&&&& cn = DriverManager.getConnection(sConnStr);
&&&&&&& }catch(Exception e){
&&&&&&&&&&& System.err.println("conndb():连接异常. " + e.getMessage());
&&&&&&& }
&&& }
&&& public static connDB getNewInstance(){
&&&&&&& return new connDB();
&&& }
&&& //数据绑定的资料好像很少,有空给大家一个例子。在这里只能返回PreparedStatement。
&&& public PreparedStatement getPreparedStmt(String sql) throws SQLException{
&&&&&&& PreparedStatement preStmt=
&&&&&&& try{
&&&&&&&&&&& preStmt = cn.prepareStatement(sql);
&&&&&&& }catch(SQLException ex){
&&&&&&&&&&& ex.printStackTrace();
&&&&&&&&&&&
&&&&&&& }
&&&&&&& return preS
&&& }
&&& public void beginTrans() throws SQLException{
&&& try{
&&&&&&&&&&& autoCommit=cn.getAutoCommit();
&&&&&&&&&&& cn.setAutoCommit(false);
&&&&&&& }catch(SQLException ex){
&&&&&&&&&&& ex.printStackTrace();
&&&&&&&&&&& System.out.print("beginTrans Errors");
&&&&&&&&&&&
&&&&&&& }
&&& }
&&& public void commit()throws SQLException{
&&&&&&& try{
&&&&&&&&&&& cn.commit();
&&&&&&&&&&& cn.setAutoCommit(autoCommit);
&&&&&&& }catch(SQLException ex){
&&&&&&&&&&& ex.printStackTrace();
&&&&&&&&&&& System.out.print("Commit Errors");
&&&&&&&&&&&
&&&&&&& }
&&& }
&&& public void rollback(){
&&&&&&& try{
&&&&&&&&&&& cn.rollback();
&&&&&&&&&&& cn.setAutoCommit(autoCommit);
&&&&&&& }catch(SQLException ex){
&&&&&&&&&&& ex.printStackTrace();
&&&&&&&&&&& System.out.print("Rollback Errors");
&&&&&&&&&&& //
&&&&&&& }
&&& }
&&& public boolean getAutoCommit() throws SQLException{
&&&&&&& boolean result=
&&&&&&& try{
&&&&&&&&&&& result=cn.getAutoCommit();
&&&&&&& }catch(SQLException ex){
&&&&&&&&&&& ex.printStackTrace();
&&&&&&&&&&& System.out.println("getAutoCommit fail"+ex.getMessage());
&&&&&&&&&&&
&&&&&&& }
&&&&&&&
&&& }
&&& //默认的情况下一次executeQuery(String sql)是一次事务。
&&& //但是可以调用beginTrans(),然后多次executeQuery(String sql),最后commit()实现多sql的事务处理(注意在这种情况下如果发生违例,千万不要忘了在catch(){调用rollBack()})。
&&& //
&&& public ResultSet executeQuery(String sql) throws SQLException{
&&&&&&& ResultSet rs =
&&&&&&& try{
&&&&&&&&&&& stmt=cn.createStatement();
&&&&&&&&&&& rs = stmt.executeQuery(sql);
&&&&&&& }
&&&&&&& catch(SQLException ex)
&&&&&&& {
&&&&&&&&&&& ex.printStackTrace();
&&&&&&&&&&& System.out.println("conndb.executeQuery:"+ex.getMessage());
&&&&&&&&&&&
&&&&&&& }
&&&&&&&
&&& }
&&& public void executeUpdate(String sql) throws SQLException{
&&&&&&& try{
&&&&&&&&&&& stmt=cn.createStatement();
&&&&&&&&&&& stmt.executeUpdate(sql);
&&&&&&& }catch(SQLException ex){
&&&&&&&&&&& ex.printStackTrace();
&&&&&&&&&&& System.out.println("conndb.executeUpdate:"+ex.getMessage());
&&&&&&&&&&&
&&&&&&& }
&&& }
&&& //Method doBatch 的参数sql,是由一些sql语句拼起来的,用;隔开。可以将许多的sql放在一个事务中,一次执行。
&&& public int[] doBatch(String sql) throws SQLException{
&&&&&&& int[] rowResult=
&&&&&&& S
&&&&&&& try{
&&&&&&&&&&& //boolean autoCommit=cn.getAutoCommit();
&&&&&&&&&&& //cn.setAutoCommit(false);
&&&&&&&&&&& stmt=cn.createStatement();
&&&&&&&&&&& StringTokenizer st = new StringTokenizer(sql,";");
&&&&&&&&&&& while (st.hasMoreTokens()){
&&&&&&&&&&&&&&&& a=st.nextToken();
&&&&&&&&&&&&&&&& stmt.addBatch(a);
&&&&&&&&&&&& }
&&&&&&&&&&&& rowResult=stmt.executeBatch();
&&&&&&& }catch(SQLException ex){
&&&&&&&&&&& ex.printStackTrace();
&&&&&&&&&&& System.out.println("conndb.doBatch:"+ex.getMessage());
&&&&&&&&&&&
&&&&&&& }
&&&&&&& return rowR
&&& }
&&& public String getDbType(){
&&&&&&& return DbT
&&& }
&&& public void close() throws SQLException{
&&&&&&& try{
&&&&&&&&&&& stmt.close();
&&&&&&&&&&& stmt=
&&&&&&&&&&& cn.close();
&&&&&&&&&&& cn=
&&&&&&& }catch(SQLException ex){
&&&&&&&&&&& ex.printStackTrace();
&&&&&&&&&&& System.out.println("Closeing connection fail"+ex.getMessage());
&&&&&&&&&&&
&&&&&&& }
&&& }
&&& public static void main(String[] args)throws Exception{
&&&&&&&&&&& connDB con=connDB.getNewInstance();
&&&&&&&&&&& System.out.println(con.getDbType());
&&&&&&&&&&& String sql2="insert into test values('','李白',80);";
&&&&&&&&&&& String s1="select *from test";
&&&&&&&&&&& con.beginTrans();
&&&&&&&&&&& ResultSet rs=con.executeQuery(s1);
&&&&&&&&&&& con.executeUpdate(sql2);System.out.println("con.executeUpdate(sql2);");
&&&&&&& /*try{
&&&&&&&&&&&& int up=s.executeUpdate(sql2);
&&&&&&&&&&&& if(up!=0)System.out.println("语句:"+sql2+"插入成功!");
&&&&&&&&&& else System.out.println("语句:"+sql2+"插入失败!");
&&&&&&& }catch(SQLException e){System.out.println(e);}*/
&&&&&&&& //ResultSet rs=s.executeQuery("select *from titles");
&&&&&&&& con.executeUpdate("delete from test where sno=''");System.out.println("con.executeUpdate(\"delete from test where sno=''\");");
&&&&&&&& con.commit();&&&&&&&
&&&&&&& while(rs.next()){&&&&&
&&&&&&&&&& System.out.print(rs.getString(1)+"\t");
&&&&&&&&&& System.out.print(rs.getString(2)+"\t");&&&&&
&&&&&&&&&&& System.out.print(rs.getInt(3)+"\t");
&&&&&&&& System.out.println(" ");&
&&&&&&&& }
&&&&&&&&&&& con.close();
&&&&&&&&&&
浏览 27864
浏览: 154534 次
来自: 深圳
谢谢 ,感觉很有用
灰常详细,受教了
(window.slotbydup=window.slotbydup || []).push({
id: '4773203',
container: s,
size: '200,200',
display: 'inlay-fix'}

我要回帖

更多关于 信息技术会考 简答题 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信