在Spring中实现事务挂起

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服务器的事务协调程序,并据此得到相应的解释。

使用事务代理

  在运行时,客户端将获取到“clinic”bean的引用,将其转换为Clinic接口,同时在它上面调用诸如loadPet或storePet之类的操作。这将隐式地使用在目标对象之前注册的“事务拦截器”检查Spring的事务代理;新的事务将被创建,然后调用将被委派给JdbcClinic目标方法。

  图1说明了一个具有“advisor链”和终端目标的AOP代理的底层概念。其中,唯一的advisor就是将事务行为包装到目标方法的事务拦截器。这是在Spring的声明性事务功能的帮助下产生的基于代理的AOP(面向方面编程)。


图1. 具有“advisor链”和终端目标的AOP代理

 

  例如,PetClinic web应用程序中的web层组件能够执行ServletContext查询操作来获取对Spring WebApplicationContext的引用,然后获得那里托管的“Clinic”bean:

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的轻量级架构结合后。在一个声明性事务是选择Local Stateless Session Bean(局部无状态会话Bean)的惟一原因的场景中,基于Spring的POJO服务模型是一个可行的选择,它提供更高水平的灵活性、可测试性和重用。

参考资料

关于作者

Juergen Hoeller是Spring Framework项目的缔造者之一。

原文出处

http://dev2dev.bea.com/pub/a/2005/07/spring_transactions.html

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服务器的事务协调程序,并据此得到相应的解释。

使用事务代理

  在运行时,客户端将获取到“clinic”bean的引用,将其转换为Clinic接口,同时在它上面调用诸如loadPet或storePet之类的操作。这将隐式地使用在目标对象之前注册的“事务拦截器”检查Spring的事务代理;新的事务将被创建,然后调用将被委派给JdbcClinic目标方法。

  图1说明了一个具有“advisor链”和终端目标的AOP代理的底层概念。其中,唯一的advisor就是将事务行为包装到目标方法的事务拦截器。这是在Spring的声明性事务功能的帮助下产生的基于代理的AOP(面向方面编程)。


图1. 具有“advisor链”和终端目标的AOP代理

 

  例如,PetClinic web应用程序中的web层组件能够执行ServletContext查询操作来获取对Spring WebApplicationContext的引用,然后获得那里托管的“Clinic”bean:

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的轻量级架构结合后。在一个声明性事务是选择Local Stateless Session Bean(局部无状态会话Bean)的惟一原因的场景中,基于Spring的POJO服务模型是一个可行的选择,它提供更高水平的灵活性、可测试性和重用。

 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值