在Spring中实现事务挂起
SpringFramework是一个流行的Java/J2EE应用框架,它构建于一个轻量级的反向控制(Inversion-of-Control,QoC)模式的容器的基础之上,以其数据访问和事务管理能力而著称。Spring的声明性事务划分适用于任何的POJO(pure old javaobject或plain ordinary Javaobject,无格式普通Java对象)目标对象,其
Spring Framework是一个流行的Java/J2EE应用框架,它构建于一个轻量级的反向控制(Inversion-of-Control,QoC)模式的 容器的基础之上,以其数据访问和事务管理能力而著称。Spring的声明性事务划分适用于任何的POJO(pure old java object或plain ordinary Java object,无格式普通Java对象)目标对象,其声明性事务如同EJB容器托管事务(Container-Managed Transaction,CMT)一样完善。后端事务管理器的选择包括简单的基于JDBC的事务和完善的J2EE事务(借助于JTA策略)。
本文详细讨论了Spring的事务管理功能。重点介绍了如何以JTA作为后端事务策略,使用Spring的针对POJO的声明性事务。本文说明 了Spring的事务服务可以与J2EE服务器的事务协调程序(如BEA WebLogic Server的事务协调程序)进行无缝交互,实际上已经成为EJB CMT的传统事务划分方式的替代方案。
针对POJO的声明性事务
为了说明Spring的声明性事务划分方式,让我们来看看Spring的PetClinic示例应用程序的中央服务外观(facade)的配置:
<bean id="dataSource"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="jndiName">
<value>java:comp/env/jdbc/petclinic</value>
</property>
</bean>
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager"/>
<bean id="clinicTarget"
class="org.springframework.samples.petclinic.jdbc.JdbcClinic">
<property name="dataSource"><ref bean="dataSource"/></property>
</bean>
<bean id="clinic"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager"><ref bean="transactionManager"/></property>
<property name="target"><ref bean="clinicTarget"/></property>
<property name="transactionAttributes">
<props>
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
<prop key="store*">PROPAGATION_REQUIRED</prop>
</props>
</property>
</bean>它遵从了Spring的标准XMLBean定义格式。它定义了:
- 一个DataSource引用,指向一个JNDI位置--这将从J2EE服务器托管的JNDI环境中获取指定的DataSource。
- 一个PlatformTransactionManage实现--在本例中,该实现指定Spring的JtaTransactionManager,它委托给J2EE服务器的事务协调程序。
- 应用程序服务实现--这是一个简单的POJO,它封装了业务和数据访问逻辑。它实现应用程序的Clinic服务接口。
- 一个应用程序服务的事务代理--该代理定义了目标服务的事务属性,提供具体的方法命名模式,并创建相应的事务。对于实际的事务管理,代理指向PlatformTransactionManager实现。
注意:Spring还通过通用属性(Commons Attribute)或者J2SE 5.0的注释(annotation),支持一种自动代理机制和对源级(source-level)元数据的使用,作为显示代理定义的替代方案。这些替代方案不在本文的讨论范围之内;其详细资料请参考Spring说明文档。
使用的服务接口和服务实现是特定于应用程序的,无需了解Spring(具体说是Spring的事务管理)就可以实现。纯Java对象可以用作目标对象,而任何一个纯Java接口都可以用作服务接口。下面是一个Clinic接口的例子:
public interface Clinic {
Pet loadPet(int id);
void storePet(Pet pet);
...
}下面显示了该接口的一个简单实现,假定它使用JDBC来执行必要的数据访问。它通过一个bean属性的setter方法接收JDBC DataSource,这直接对应上面配置中的dataSource属性定义。
public class JdbcClinic implements Clinic {
private DataSource dataSource;
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}
public Pet loadPet(int id) {
try {
Connection con = this.dataSource.getConnection();
...
}
catch (SQLException ex) {
...
}
}
public void storePet(Pet pet) {
try {
Connection con = this.dataSource.getConnection();
...
}
catch (SQLException ex) {
...
}
}
...
}正如您所看到的,代码简单明了。使用了一个简单Java对象。事务管理由事务代理处理,我们随后再对其进行说明。
注意,PetClinic示例应用程序中实际的基于JDBC的Clinic实现利用了Spring的JDBC支持类,以免只工作在简单的JDBC API级别上。但是,Spring的事务管理还将使用简单的基于JDBC的实现,比如上面的实现。
定义事务代理
除JdbcClinic实例之外,配置还为其定义了一个事务代理。如果需要,可以显式地指定该事务代理所暴露的实际接口。默认状态下,目标对象实现的所有接口都将被暴露--在本例中是应用程序的Clinic服务接口。
从客户端的角度来看,“clinic”bean只是应用程序的Clinic接口的实现。客户端不必知道自己正在和事务代理打交道。这就是接口的力量:目标对象的直接引用可以很轻松地由实现了相同接口的代理取代--在本例中是一个隐式地创建事务的代理。
对于特定的方法或方法命名模式,代理的具体事务行为由事务属性驱动,如下面的例子所示:
<prop key="load*">PROPAGATION_REQUIRED,readOnly</prop><prop key="store*">PROPAGATION_REQUIRED</prop>key属性确定代理应该给哪个方法增加事务行为。这样的属性最重要的部份是传播行为。有以下选项可供使用:
- PROPAGATION_REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
- PROPAGATION_NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
前六个策略类似于EJB CMT:常量名相同,因此,对EJB开发人员来说,应该立刻就感到熟悉。第七个(PROPAGATION_NESTED)是Spring所提供的一个特殊变量。它要求事务管理器或者使用JDBC 3.0 Savepoint API提供嵌套事务行为(如Spring的DataSourceTransactionManager),或者通过JTA支持嵌套事务。
事务属性中的readOnly标志表示对应的事务应该被最优化为只读事务。这是一个最优化提示。在一些情况下,一些事务策略能够起到显著的最优化效果,例如在使用Object/Relational映射工具(如:Hibernate或TopLink)时避免dirty checking(试图“刷新”)。
在事务属性中还有定义“timeout”值的选项,指定事务超时为几秒。在JTA中,这将被简单地传递到J2EE服务器的事务协调程序,
WebApplicationContext ctx =
WebApplicationContexUtils.getWebApplicationContext(servletContext);
Clinic clinic = (Clinic) ctx.getBean("clinic);
Pet pet = new Pet();
pet.setName("my new cat");
clinic.storePet(pet);在storePet()调用的开始,Spring的事务代理将隐式地创建一个事务。在storePet()调用返回时,将提交或回滚事务。默认情况下,任何RuntimeException或Error的抛出均会导致回滚。可以指定何时提交和何时回滚的实际规则:Spring的事务属性支持一个称为“回滚规则”的概念。
例如,我们可以引入一个检查性的PetClinicException,并告诉事务代理,在抛出该异常时执行进行回滚。
<prop key="load*">PROPAGATION_REQUIRED,readOnly,-PetClinicException</prop>
<prop key="store*">PROPAGATION_REQUIRED,-PetClinicException</prop>还有一个类似的“提交规则”,指定了触发提交的具体异常。
注意,上面所显示的显式查询仅仅是用来访问Spring托管的bean的通用变体,它用于任何一种web资源中,比如servlet或filter。在使用Spring固有的web MVC框架构建web层时,这些bean可以被直接插入到web控制器中。其他的一些应用框架也支持对Spring bean的便捷访问,例如Struts、WebWork、JSF和Tapestry。详细资料请参考Spring说明文档。
PlatformTransactionManager策略
Spring事务支持中的核心接口是org.springframework.transaction.PlatformTransactionManager。为了实际执行事务,Spring所有的事务划分功能都通过传递适当的TransactionDefinition实例,委托给PlatformTransactionManager。尽管PlatformTransactionManager接口可以直接使用,应用程序通常配置具体的事务管理器并使用声明性事务来划分事务。
Spring具有多种PlatformTransactionManager实现,它们分为两类:
- 局部事务策略--针对单个资源执行事务(在多数情况下是针对单个的数据库)。例子有org.springframework.jdbc.datasource.DataSourceTransactionManager和org.springframework.orm.hibernate.HibernateTransactionManager。
- 全局事务管理--执行有可能跨越多个资源的全局事务。主要对应的Spring类是org.springframework.transaction.jta.JtaTransactionManager,它委托给遵循JTA规范的事务协调程序(通常是J2EE服务器,但不一定)。
PlatformTransactionManager抽象化的主要意义在于,应用程序并不限定于某个特定的事务管理环境。相反,通过选择PlatformTransactionManager接口的不同实现类,可以很容易地切换事务策略。这允许应用程序代码和声明性事务划分保持不变,不管应用程序组件用在哪种环境中。
例如,应用程序的一个基础版本可能部署到Tomcat环境中,并与一个Oracle数据库交互。它可以使用便捷的Spring事务划分,选择JDBC DataSourceTransactionManager作为其事务策略。Spring将划分事务,而且JDBC驱动程序将执行对应的纯JDBC事务。
而这个应用程序的另外一个版本也许部署在WebLogic Server环境中,与两个Oracle数据库交互。但是,应用程序代码和事务划分无需更改。唯一需要调整的是选择JtaTransactionManager作为事务策略,让Spring划分事务,而WebLogic Server的事务协调程序执行事务。
JTA UserTransaction与JTA TransactionManager
让我们来看一些Spring的JTA支持的详细情况。虽然理解该机制是很有帮助的,但是通常不必为之担心。对于像前面的小节所展示的简单用例,只需要一个标准的JtaTransactionManager定义,以及由J2EE服务器提供的支持XA规范的DataSource。
默认的Spring JtaTransactionManager设置将从标准的JNDI位置获取JTA的javax.transaction.UserTransaction对象,该JNDI位置由J2EE指定:java:comp/UserTransaction。对于大多数标准J2EE环境下的用例来说,它工作良好。
但是,默认的JtaTransactionManager不能执行事务挂起操作(即它不支持PROPAGATION_REQUIRES_NEW和PROPAGATION_NOT_SUPPORTED)。原因是标准的JTA UserTransaction接口不支持挂起或恢复事务的操作;它只支持开始和完成新事务的操作。
为执行事务挂起操作,还需要提供javax.transaction.TransactionManager实例,按照JTA的规定,它提供标准的挂起和恢复方法。遗憾的是,J2EE没有为JTA TransactionManager定义标准的JNDI位置!因此,必须使用特定于供应商的(vendor-specific)查寻机制。
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName">
<value>vendorSpecificJndiLocation</value>
</property>
</bean>实质上,J2EE没有考虑把JTA TransactionManager接口作为它的公开API的一部分。JTA规范规定的TransactionManager接口原本是打算用于容器集成的。虽然这可以理解,但是为JTA TransactionManager定义标准的JNDI位置还是有重大意义的,尤其是对于轻量级容器(如Spring);然后,便可以以同样的方式来定位任意的J2EE服务器的JTA TransactionManager。
不仅Spring的JtaTransactionManager将从对JTA TransactionManager的访问中获益,而且O/R映射工具,比如Hibernate、Apache OJB和Kodo JDO也将从中受益,因为他们需要利用该接口在JTA环境中执行缓存同步--即在JTA事务完成时释放缓存锁。注册事务同步的能力只能由JTA TransactionManager接口而不是UserTransaction句柄提供。因此,每个工具都需要实现自己特定于供应商的TransactionManager查寻适配器。
为JTA TransactionManager定义标准的JNDI位置是许多基础架构软件供应商在J2EE方面的强烈愿望。如果J2EE 5.0规范的开发团队能意识到该功能的重要性,那就太好了。幸运的是,优秀的J2EE服务器(如WebLogic Server)已经考虑将其JTA TransactionManager作为公开的API,包括特定于供应商的扩展!
结合了WebLogic JTA的Spring事务划分
对于WebLogic Server,JTA TransactionManager的正式JNDI位置是javax.transaction.TransactionManager。在Spring的JtaTransactionManager中,该值可被指定为“transactionManagerName”。一般来说,这启用了使用WebLogic JTA子系统的Spring驱动的事务挂起,激活了对PROPAGATION_REQUIRES_NEW和PROPAGATION_NOT_SUPPORTED的支持。
<bean id="transactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManagerName">
<value>javax.transaction.TransactionManager</value>
</property>
</bean>除了标准的JtaTransactionManager和它所支持的通用配置选项,Spring还支持一种特殊的WebLogicJtaTransactionManager适配器,它直接利用WebLogic的JTA扩展。
<bean id="transactionManager" class="org.springframework.transaction.jta.WebLogicJtaTransactionManager"/>
除了自动检测WebLogic的JTA TransactionManager,它还支持不属于标准JTA的三个重要特性:
- 事务名称(Transaction name)--将Spring的事务名称暴露给WebLogic Server,使Spring事务在WebLogic的事务监视器上可见。默认的情况下,Spring将对声明性事务使用全限定方法名。
- 按事务的隔离级别(Per-transaction isolation level)--将Spring事务属性中指定的隔离级别应用到WebLogic JTA事务。这支持按事务指定数据库的隔离级别,而标准的JTA并不支持。
- 强制性事务恢复(Enforcing transaction resume)--恢复WebLogic事务,即使挂起的事务已标记为rollback-only(只能回滚)。这要求调用forceResume()方法,使用WebLogic底层的扩展的TransactionManager接口。
下面的图像显示的是WebLogic Server的事务监视器,它按名称列出了一组Spring驱动的事务:
图2. WebLogic Server的事务监视器(点击图片查看大图)
Spring的WebLogicJtaTransactionManager实际上暴露了WebLogic Server的事务管理器针对基于Spring的应用程序的全部功能。它使Spring事务划分成为EJB CMT的一个极具吸引人的替代方案,而且它提供同级别的事务支持。
注意,只有实际需要挂起事务或使用WebLogic的JTA扩展时,才需要对特定于WebLogic的JTA进行设置。对于标准的事务划分(如PROPAGATION_REQUIRED或PROPAGATION_SUPPORTS),标准的JTA设置就足够了。
Spring和EJB CMT
如上所述,针对POJO的Spring声明性事务划分可以看作传统的EJB CMT的替代方案。但是,Spring和EJB并不是互相排斥的。Spring应用程序上下文也可以作为EJB外观的后端,管理数据访问对象(DAO)和其它的细粒度业务对象。
在EJB场景中,事务由EJB CMT驱动。Spring的数据访问支持会自动地检测这样的环境并进行相应的调整。例如,Spring的Hibernate支持将为其隐式资源管理提供EJB驱动的事务,就像它提供Spring驱动的事务一样。它甚至提供相同的语义,而不需要对DAO代码做任何修改。
Spring有效地将DAO实现从实际的运行时环境中分离出来。DAO可以参与到Spring事务(以哪个事务策略作为后端都可以)和EJB CMT事务中。这不仅支持其它环境中的重用,还支持在J2EE容器之外的测试中直接使用。
结束语
Spring Framework为J2EE和非J2EE环境提供了完善的事务划分功能,具体来说就是为纯Java目标对象提供声明性事务。这允许在没有EJB的情况下,以一种灵活和非入侵的方式便捷地进行事务划分。与EJB相比,这些事务POJO应用程序对象可以在J2EE容器之外轻松地测试或重用。
Spring提供了多种开箱即用的事务策略,比如JtaTransactionManager和JDBC DataSourceTransactionManager,前者委托给J2EE服务器的事务协调程序,后者则针对单个JDBC DataSource(即单个的目标数据库)执行事务。通过对后端配置进行简单的更改,Spring就能够轻松地调整事务策略适应另一个环境。
除了标准的JTA支持,Spring还提供与WebLogic Server的JTA扩展的完善集成,支持一些高级特性(如事务监控和按事务的隔离级别)。通过这种专门的WebLogic Server支持,WebLogic的事务管理器的全部功能都可应用于基于Spring的应用程序。
Spring事务划分是对EJB CMT的极具吸引人的替代方案,特别是与基于POJO的轻量级架构结合后
更多推荐
所有评论(0)