第9章Spring中的事务管理技术及实现(第2/3部分)
1.1.1Spring框架中与事务管理相关的API
1、org.springframework.transaction.PlatformTransactionManager接口
(1)PlatformTransactionManager接口的定义
【例9-6】PlatformTransactionManager接口定义的代码示例
package org.springframework.transaction ;
public interface PlatformTransactionManager{
void commit(TransactionStatus status);
TransactionStatus getTransaction(TransactionDefinition definition);
void rollback(TransactionStatus status);
}
在该接口中,定义了对事务管理和控制的主要的方法,如commit 和rollback等。开发者借助于该接口的实现类来达到对应用系统中的数据访问组件进行事务管理和控制。所有Spring的事务分离功能都会委托给PlatformTransactionManager(但实际是传给相应的TransactionDefinition类的对象实例)来做实际的事务执行。
(2)PlatformTransactionManager接口的各个实现类
通过查看Spring框架所提供的API帮助文档,可以了解到PlatformTransactionManager接口有下面的几种不同形式的实现类。之所以提供这么多的不同的实现类,其目的无非还是能够真正实现应用系统与底层的事务资源的无关性,从而也达到实现前面所介绍的Spring事务管理的底层实现是采用对事务资源的抽象设计方案的目标。
1)DataSourceTransactionManager(针对标准的JDBC技术)
2)JtaTransactionManager(针对JTA技术)
3)HibernateTransactionManager(针对Hibernate O/R Mapping技术)
4)JdoTransactionManager(针对JDO技术)
5)PersistenceBrokerTransactionManager(针对OJB技术)
6)JmsTransactionManager(针对J2EE JMS技术)
注意:
无论是单个还是多个事务资源管理器,都可以使用JtaTransactionManager类。但如果使用JtaTransactionManager,那么所有事务管理实际都会委托给底层应用服务器的JTA来实现。
(3)PlatformTransactionManager接口的各个实现类是对相应的事务资源的包装为了能够让读者真正理解Spring框架在事务方面是如何达到让应用系统与底层的事务资源的无关性的目标,通过查看和阅读Spring框架中所携带的源代码,可以发现这些XXXXManager类在具体事务的技术实现方面,其实就是简单地调用相应的事务资源中所提供的事务管理和控制的操作方法。
读者从下面【例9-7】中所示的HibernateTransactionManager 的代码示例中,很明显地能够了解到上面所描述的特性,并请注意其中的黑体部分的代码。
【例9-7】 HibernateTransactionManager类的部分代码示例
package org.springframework.orm.hibernate3;
// import 语句,在此加以省略
public class HibernateTransactionManager extends AbstractPlatformTransactionManager implements BeanFactoryAware, InitializingBean {
protected void doCommit(DefaultTransactionStatus status) {
HibernateTransactionObject txObject =
(HibernateTransactionObject) status.getTransaction();
//。。。其它的语句代码,在此加以省略
try{
txObject.getSessionHolder().getTransaction().commit();
}
//。。。其它的语句代码,在此加以省略
}
protected void doRollback(DefaultTransactionStatus status){
HibernateTransactionObject txObject =
(HibernateTransactionObject) status.getTransaction();
//。。。其它的语句代码,在此加以省略
try{
txObject.getSessionHolder().getTransaction().rollback();
}
//。。。其它的语句代码,在此加以省略
}
protected void doSetRollbackOnly(DefaultTransactionStatus status) {
HibernateTransactionObject txObject =
(HibernateTransactionObject) status.getTransaction();
//。。。其它的语句代码,在此加以省略
txObject.setRollbackOnly();
}
//。。。其它的方法的定义代码,在此加以省略
}
比如下面的【例9-8】中所示的DataSourceTransactionManager的代码示例也更进一步地验证了该实现方式,同样也请注意其中的黑体部分的代码。
【例9-8】 DataSourceTransactionManager类的部分代码示例
package org.springframework.jdbc.datasource;
// import 语句,在此加以省略
public class DataSourceTransactionManager extends
AbstractPlatformTransactionManager implements InitializingBean { protected void doCommit(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject =
(DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
//。。。其它的语句代码,在此加以省略
try{
https://www.wendangku.net/doc/5a18952134.html,mit();
}
//。。。其它的语句代码,在此加以省略
}
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject =
(DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
//。。。其它的语句代码,在此加以省略
try{
con.rollback();
}
//。。。其它的语句代码,在此加以省略
}
protected void doSetRollbackOnly(DefaultTransactionStatus status){
DataSourceTransactionObject txObject =
(DataSourceTransactionObject) status.getTransaction();
//。。。其它的语句代码,在此加以省略
txObject.setRollbackOnly();
}
}
(4)PlatformTransactionManager接口的主要作用
在Spring框架中提供该接口的主要目的是实现分离应用系统中的事务管理的具体技术实现不会被限定于某个特定的事务管理实现的环境和平台(其实是应用了依赖倒置原则DIP,Dependency Inversion Principle,请见下面的图9.1中的原理示图)。这样,开发者可以根据应用系统本身的实际运行的环境而合理地选择PlatformTransactionManager接口的不同实现类,并且还可以很容易地切换事务实现的策略。
图9.1 依赖倒置原则DIP的原理示图
2、PlatformTransactionManager接口的各个实现类的具体应用
通过采用Spring框架中对事务的支持,应用系统的开发者在开发过程中和系统的实际运行环境中所采用的环境可以并一致,从而简化开发过程和提高开发的效率。当应用系统开发完成后,只需要改变Spring的IoC的XML外部配置文件来进行运行环境的切换。(1)如果使用JDBC或iBATIS作为系统的事务资源管理器
此时开发者可以使用简单的DataSourceTransactionManager事务资源管理器实现类,其配置标签的示例请见下面的【例9-9】所示,需要为其提供DataSource对象。
【例9-9】DataSourceTransactionManager事务资源管理器实现类的应用示例 class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
(2)如果使用Hibernate框架作为系统的事务资源管理器
此时开发者可以使用HibernateTransactionManager事务资源管理器实现类,其配置标签的示例请见下面的【例9-10】中所示的内容,需要为其提供SessionFactory对象的实例。【例9-10】HibernateTransactionManager事务资源管理器实现类的应用示例 class="org.springframework.orm.hibernate. HibernateTransactionManager">
(3)如果使用WebSphere应用服务器的JTA支持
此时开发者只需要把上述对应Bean定义中的class属性改成为下面的org.springframework.transaction.jta.JtaTransactionManager,然后再把属性改为WebSphere对应的TransactionManager实现类,其配置标签的示例请见下面的【例9-11】所示,需要为其transactionManager属性提供WebSphereTransactionManagerFactoryBean 对象实例。
【例9-11】JtaTransactionManager分布式事务资源管理器实现类的应用示例 "org.springframework.transaction.jta.WebSphereTransactionManagerFactoryBean"/> class="org.springframework.transaction.jta.JtaTransactionManager"> 1.2在Spring中实现编程式的事务管理 1.2.1Spring中提供的对编程式的事务管理的支持 1、传统的JDBC事务管理中编程式的事务管理实现 (1)对每个请求都是从数据源中重新取出一个连接 以往使用JDBC进行数据操作时,一般采用DataSource接口并通过实现了DataSource 接口的数据库连接池实现对物理数据库的最终的连接(获得从数据源中得到的Connection 对象实例)。而数据源DataSource是线程安全的,但数据库的数据连接对象Connection 实例不是线程安全的,因此必须对每个数据访问的请求都需要重新从数据源中获得一个数 据库的连接对象实例。 当然,由于JDBC中的DataSource为接口,一般的DataSource接口的数据源具体实现可以是由J2EE应用服务器容器来提供实现和数据库连接以及包括连接池等方面的管理。例如Tomcat、WebSphere 和WebLogic等J2EE应用服务器容器中都提供了对DataSource接口的具体实现。 但由于J2EE应用服务器容器所提供的DataSource接口的数据源具体实现,要求开发者通过JNDI来获得其具体的数据库连接Connection对象实例,这样将使得应用系统的持久层中的DAO组件将紧密地依赖某种J2EE应用服务器容器。因此,开发者也可以应用类似Apache的DBCP这样中立的DataSource接口的数据源具体实现。 (2)JDBC标准的事务管理实现的代码 为了对比JDBC和Spring框架两者在事务管理实现的代码方面的差别,下面在【例9-12】中给出一个基于JDBC标准的事务管理实现的模板代码示例。 【例9-12】基于JDBC标准的事务管理实现的模板代码示例 Connection conn = null; try{ conn = DBConnectionFactory.getConnection; conn.setAutoCommit(false); //(1) 缺省方式是自动提交 //完成对数据库的修改操作,在此没有列出细节代码 https://www.wendangku.net/doc/5a18952134.html,mit(); //(2)自己提交(确认数据修改的行为)} catch(Exception e){ conn.rollback(); //(3) 恢复修改(回滚) //进行异常处理 } finally{ try{ conn.close(); //(4) 关闭数据库连接 } catch(SQLException se){ //进行异常处理 } } (3)JDBC标准的事务管理实现代码的主要缺点 从上面的JDBC标准的事务管理实现代码中可以了解到,不仅所编程实现的代码量比较长,而且也很容易疏忽或者忘掉一些异常捕获等的try/catch语句。而且还必须在进行数据访问之前将JDBC的自动提交模式改变为手动提交模式(conn.setAutoCommit(false);的功能)。 2、Spring 框架DAO组件中的JdbcTemplate类的缺省的事务管理模式 (1)Spring框架对JDBC事务管理方式进行了进一步的包装 对此读者通过前面的【例9-8】中的DataSourceTransactionManager类的部分代码示例中应该能够理解,这样将能够在一定的程度上简化标准的JDBC的编程实现。因为,在Spring框架中还提供了几个与编程实现的事务处理技术相关的一些帮助类。 1)TransactionDefinition,实现对事务的属性进行定义 2)TranscationStatus,它代表了当前的事务运行的状态,辅助事务提交和回滚等操作 3)PlatformTransactionManager接口,它具体实现对事务的管理和控制行为 这些帮助类是Spring框架提供的用于管理事务的基础API,由于PlatformTransactionManager为接口,因此对该接口提供如下的一个实现的抽象类AbstractPlatformTransactionManager,并且在该类的基础上又针对不同的事务资源提供了具体的实现类,比如在下面所要使用的事务管理类DataSourceTransactionManager等都是这个AbstractPlatformTransactionManager类的子类。 (2)JdbcTemplate类的缺省的事务管理模式同样也采用JDBC默认的AutoCommit模式由于在Spring 框架的DAO组件中的JdbcTemplate类中的各种数据访问操作方法采用的是JDBC默认的自动提交(AutoCommit)模式,因此如果在实现某个转帐系统中采用下面的示例中的代码,将同样也无法保证数据操作的原子性。 【例9-13】本示例中的代码将也无法保证数据操作的原子性 JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update("Update InComeCount set money=money-100 where id = '1234'"); jdbcTemplate.update("Update OutComeCount set money=money-100 where id = '1234'"); 其主要的错误原因是由于在Spring框架的DAO组件中的JdbcTemplate类采用了AutoCommit模式,因此当第一条update的数据操作完成之后将被自动提交,此时的数据库表中用户ID号为“1234”的对应的记录中的数据已经被更新,如果第二条SQL语句的操作出现了失败,系统将无法使得整个事务回滚到最初的状态。 (3)利用DataSourceTransactionManager类实现代码控制的事务管理 org.springframework.jdbc.datasource.DataSourceTransactionManager类为JDBC DataSource类型的数据源的事务管理组件。 对于该组件的应用,开发者只需要在Spring 框架IoC容器的XML配置的Bean的定义配置文件中对它进行简单的配置,然后将其通过属性依赖注入引入到应用系统的DAO组件类中。 3、使用Spring 框架编程式的事务管理的基本流程 (1)首先声明一个基于DataSource接口的数据源的实现类的对象实例 (2)其次声明一个事务管理类,可以在下面的几种形式中根据应用系统的实际数据访问平台来合理地选择其一,例如DataSourceTransactionManager、HibernateTransactionManger 和JTATransactionManager等事务管理的具体实现类。 (3)最后在应用系统的有关的组件代码中加入事务处理控制的相关的代码 4、使用Spring 框架编程式的事务管理的代码示例 在下面的【例9-14】中的Spring 框架编程式的事务管理的代码示例中,其编程的代码基本上是按照前面所描述的流程来实现的。其中的dataSource对象实例是通过依赖注入而获得的。当然,开发者也可以直接将JdbcTemplate类的对象实例以属性注入的方式依赖注入进来) 【例9-14】Spring 编程式的事务管理的代码示例 TransactionDefinition td = new TransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(td); try{ JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource); jdbcTemplate.update("Update InComeCount set money=money-100 where id = '1234'"); jdbcTemplate.update("Update OutComeCount set money=money-100 where id = '1234'"); https://www.wendangku.net/doc/5a18952134.html,mit(status); } catch(Exception e){ transactionManager.rollback(status); } 1.2.2在Spring框架中实现编程式的事务管理的实例 下面通过某个项目中的持久层中的DAO组件中的某个数据访问方法中应用事务和不应用事务所出现的各种结果,让读者体验事务中的“原子性”、“一致性”和Spring框架中的编程式的事务管理的具体实现步骤。 1、对DAO组件中的某个数据访问方法不应用事务控制技术 在下面的DAO组件中的updateOneUserInfo方法中实现对用户的信息进行修改,但为了能够应用事务控制技术,故意执行两次修改并且第二次的修改中的Update的SQL语句中的数据库表名称写错误,这样将导致第二次的修改操作是无法成功的。然后再观察对比应用事务和不应用事务所出现的各种不同的结果,请注意其中的黑体部分的代码。 【例9-14】对updateOneUserInfo数据访问方法不应用事务控制技术的代码示例public boolean updateOneUserInfo(UserInfoVO oneUserInfoVO, String newUserPassWord){ String userName=oneUserInfoVO.getUserName(); String userPassWord=oneUserInfoVO.getUserPassWord(); Object parameter[]={newUserPassWord,userName,userPassWord}; String updateSQL1 = "update userInfo set userPassWord = ? where userName =? and userPassWord=?"; String updateSQL2 = "update userInfo2set userPassWord = ? where userName =? and userPassWord=?"; this.jdbcTemplate.update(updateSQL1,parameter); this.jdbcTemplate.update(updateSQL2,parameter); return true; } 然后启动对用户信息进行修改的表示层的页面,并输入新的用户信息。请见下面的图9.2中所示的状态。 图9.2 启动对用户信息进行修改的表示层的页面 当在页面中进行提交后,将出现下面的图9.3中所示的错误提示结果。 图9.3 执行后所产生的错误提示结果 其错误的主要原因是第二条SQL语句中的数据库表名称是不存在的,因此在数据访问中将产生出错误,这从所显示的错误提示信息页能够反映出该状态。但由于在updateOneUserInfo中的第一次的数据修改是成功的,因此用户的密码仍然会被修改。因为,此时没有应用事务控制,当然对数据访问的“一致性”将无法达到保证。 执行后,再打开具体的数据库表,发现用户的密码信息仍然被修改!请见下面的图9.4中的所示的结果(原来的用户的密码1234被改变为45678)。 图9.4 打开数据库表后发现用户的密码信息仍然被修改 2、对DAO组件中的该数据访问方法应用事务控制技术 (1)在DAO类DAOImple中添加一个PlatformTransactionManager 类型的transactionManager属性,并且提供set方法 【例9-15】添加transactionManager属性和提供set方法 private PlatformTransactionManager transactionManager; public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } 下面的图9.5为开发过程中的程序代码的显示情况。 图9.5 在DAO组件中添加transactionManager属性和提供set方法 (2)修改DAO组件中的updateOneUserInfo方法以应用事务技术 【例9-16】在DAO组件updateOneUserInfo方法中应用事务控制技术的代码示例public boolean updateOneUserInfo(UserInfoVO oneUserInfoVO, String newUserPassWord){ String userName=oneUserInfoVO.getUserName(); String userPassWord=oneUserInfoVO.getUserPassWord(); Object parameter[]={newUserPassWord,userName,userPassWord}; String updateSQL1 = "update userInfo set userPassWord = ? where userName =? and userPassWord=?"; String updateSQL2 = "update abcTable set userPassWord = ? where userName =? and userPassWord=?"; DefaultTransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def); try{ this.jdbcTemplate.update(updateSQL1,parameter); this.jdbcTemplate.update(updateSQL2,parameter); } catch (DataAccessException ex) { // 也可以执行status.setRollbackOnly(); transactionManager.rollback(status); throw ex; } https://www.wendangku.net/doc/5a18952134.html,mit(status); return true; } 请注意其中的黑体部分的示例代码,为了能够观察应用了事务处理技术后的效果,在代码中仍然对数据库表进行了两次修改,其中第二次修改应该是失败的(因为故意在数据库表名称上写错,这使得SQL语句不合语法,因而造成DataAccessException(它封裝了SQLException)的异常抛出,这个异常被catch捕捉,因而执行rollback()而取消前面的正确执行SQL的操作结果,如果沒有发生错误,则最后我们使用commit()来提交操作。(3)DAO组件DAOImple类的完整的代码 为了能够让读者真正掌握如何在Spring框架中实现编程式的事务管理的代码,下面给出DAO组件DAOImple类的完整的代码。请见下面的【例9-17】中的代码示例。 【例9-17】 DAO组件DAOImple类的完整的代码示例 package com.px1987.springwebapp.dao; import https://www.wendangku.net/doc/5a18952134.html,erInfoVO; import org.springframework.jdbc.core.*; import java.util.*; import org.springframework.transaction.support.DefaultTransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.dao.DataAccessException; import org.springframework.transaction.PlatformTransactionManager; public class DAOImple implements DAOInterface { private PlatformTransactionManager transactionManager; public void setTransactionManager(PlatformTransactionManager transactionManager) { this.transactionManager = transactionManager; } JdbcTemplate jdbcTemplate=null; public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } public DAOImple(){ } public boolean updateOneUserInfo(UserInfoVO oneUserInfoVO, String newUserPassWord){ String userName=oneUserInfoVO.getUserName(); String userPassWord=oneUserInfoVO.getUserPassWord(); Object parameter[]={newUserPassWord,userName,userPassWord}; String updateSQL1 = "update userInfo set userPassWord = ? where userName =? and userPassWord=?"; String updateSQL2 = "update abcTable set userPassWord = ? where userName =? and userPassWord=?"; DefaultTransactionDefinition def = new DefaultTransactionDefinition(); TransactionStatus status = transactionManager.getTransaction(def); try{ this.jdbcTemplate.update(updateSQL1,parameter); this.jdbcTemplate.update(updateSQL2,parameter); } catch (DataAccessException ex){ transactionManager.rollback(status); throw ex; } https://www.wendangku.net/doc/5a18952134.html,mit(status); return true; } } 3、修改springapp-servlet.xml配置文件添加下面的与事务相关的一些配置项目 在下面的【例9-18】中所示的springapp-servlet.xml配置文件中,主要是添加DriverManagerDataSource、JdbcTemplate、DAOImple和DataSourceTransactionManager 等类的对象实例的定义,并将各个相关的对象实例以属性注入的方式注入到目标组件类中。【例9-18】在项目的Spring IoC的XML配置文件中添加与事务相关的一些配置标签 "https://www.wendangku.net/doc/5a18952134.html,/dtd/spring-beans-2.0.dtd"> class="org.springframework.jdbc.datasource.DriverManagerDataSource">
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
4、再次执行本项目中的用户信息修改的页面以观察数据库表中的数据是否被修改
(1)在浏览器中输入下面的URL地址
将出现下面的图9.6中所示的状态,继续与前面的操作方式一样,输入相关的用户信息。原始的密码为45678(在前面的示例中所产生的结果基础上继续本示例),并输入新的密码123。
图9.6 输入相关的用户信息
(2)在浏览器中仍然会出现下面的图9.7中的错误
图9.7 在浏览器中仍然会出现错误
该错误提示与前面的图9.3中所示的错误相同。但再次打开数据库表,发现用户的密码并没有被修改!原始的密码仍然为45678,并没有被改变为新的密码123。请见下面的图9.8中所示的状态。
图9.8打开数据库表后发现用户的密码信息没有被修改
注意:
事务管理的控制最好应该放到数据访问服务组件(DAO Service)中,本示例只是说明Spring框架的事务管理的技术实现。
(3)将前面【例9-16】中所示代码中的updateSQL2改变为下面的内容
String updateSQL2 = "update userInfo1 set userPassWord = ? where userName =?";
也就是将数据库表名称由“userInfo1”改变为正确的“userInfo”,再继续执行本应用,将发现能够正确地对用户的密码进行修改。