第五部分:数据访问

这部分的参考文档涉及数据访问和数据访问层和业务或服务层之间的交互。

Spring的综合事务管理支持覆盖很多细节,然后覆盖了各种数据访问框架和Spring框架集成的技术。

事务管理

1. Spring框架事务管理介绍

综合事务支持是使用Spring框架最吸引人的理由之一。Spring框架为事务管理提供了一致的抽象,有以下好处:

  • 对各种不同的事务APIs,比如Java Transaction API(JTA),JDBC,Hibernate,Java Persistence API(JPA)和Java Data Objects(JDO)有一致的编程模型
  • 支持声明式事务
  • 提供了更简单的编程事务管理,相比复杂的事务APIs比如 JTA。
  • 与Spring的数据访问抽象有很极好的集成

下面章节介绍Spring框架的事务value-adds和技术。(也包括最佳编程讨论,应用服务集成,和解决通用问题)

  • Spring框架的事务支持模型的优势描述了为什么应该使用Spring框架的事务抽象,而不是EJB Container-Managed Transactions(CMT)或者选择通过专有的API驱动本地事务,比如Hibernate。
  • 了解Spring框架事务抽象概括了核心类和描述了如何配置和从不同的来源获取DataSource
  • 事务同步资源描述应用代码如何保证资源被创建,重复使用和正确的清理。
  • 声明式事务管理描述了对声明式事务管理的支持
  • 编程事务管理覆盖了对编程事务管理的支持。
  • 事务绑定事件描述了如何在一个事务中使用应用事件

2.Spring框架的事务支持模型的优势

传统地,JavaEE开发者对事务管理有两个选择:全局或者局部事务,两者都有很大的局限性。接下来会介绍全局和局部事务管理,接着是Spring框架的事务管理支持如何解决全局和局部事务模型的局限性。

1. 全局事务

全局事务保证你能处理多个事务资源,通常是关系型数据库和消息队列。应用服务管理全局事务通过JTA,一个有着笨重的API来使用(部分是因为它的异常模型)。此外,JTAUserTransaction正常需要从JNDI的获取源,意外着为了使用JTA,还得使用JNDI。很显然使用全局事务会限制应用程序代码的潜在重复使用,因为JTA通常只能在应用服务环境里使用。

以前,使用全局事务更偏爱的方式是通过EJB CMT(Container Managed Transaction):CMT是声明式事务的形式(与程序式事务管理有区别)。EJB CMT移除了需要为事务相关的JNDI查找,当然使用EJB本身需要JNDI。它删除了大多数,但不是全部需要java代码去控制的事务。这显著的缺点是CMT与JTA和应用服务环境绑在了一起。并且,它仅仅在EJBs里选择实现业务逻辑才可用,或者至少基于事务EJB表面。EJB的底盘很大,但不是吸引人的建议,尤其是面对吸引人的声明式事务。

2.局部事务

局部事务是特定资源的,比如与JDBC连接的事务。本地事务可能更容易使用,但是有明显的缺点:他们不能与多个事务资源合作。比如,使用JDBC连接的事务管理代码不能在全局JTA事务里运行。因为应用服务没有参与事务管理,不能保证跨多个资源的正确性(值得注意的是大多数应用程序使用单一的事务资源),另一个缺点是局部事务侵入到了编程模型。

3.Spring框架的一致编程模型

Spring解决了全局和局部事务的缺点。它可以让应用开发者在任何环境里使用一致编程模型。写一遍代码,就可以在不同的环境里从不同的事务管理策略里受益。Spring框架提供了声明式和编程式事务管理。大多数用户喜欢声明式事务管理,也是在大多数场景推荐的。

有了编程式事务管理,开发人员使用Spring框架事务抽象,它可以运行在任何底层事务基础设施。使用更好的声明式模型,开发者通常几乎不用写和事务管理的代码,并且因此不依赖Spring框架事务API,或者任何其他事务API。

事务管理需要应用服务吗?
Spring框架事务管理支持改变传统规则,当企业Java应用需要一个应用服务时。

特别是,仅仅为了声明式事务,不需要通过EJBs使用应用服务器。事实上,即使应用服务器有很强大的JTA能力,相比EJB CMT,Spring框架声明式事务提供更强大和更有成效的编程式模型。

一般的,需要应用服务器的JTA能力仅仅是应用需要处理多个资源的事务,而不是需要很多应用。很多高端应用使用单一的、高度可伸缩的数据库(比如Oracle RAC)。独立的事务管理比如Atomikos Transactions和JOTM是另外的选择。当然,可能需要应用服务器的其他能力,比如 Java Message Service(JMS)和Java EE Connector Architecture(JCA).

Spring框架当需要扩展应用到全功能应用服务器给你选择。唯一代替使用EJB CMT或者JTA与局部事务写代码,比如在JDBC连接上,和面对需要在全局,容器管理事务的运行代码的高度重复工作一去不复返了。有了Spring框架,只有一些在你配置文件里定义的bean,而不是代码,需要改变。

3.了解Spring框架事务抽象

Spring事务抽象的关键是事务策略的概念。事务策略通过org.springframework.transaction.PlatformTransactionManager接口定义:

public interface PlatformTransactionManager {
    TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
     void commit(TransactionStatus status) throws TransactionException;
     void rollback(TransactionStatus status) throws TransactionException;
}

这主要是一个服务提供程序接口(SPI),虽然可以从应用代码里使用编程方式。因为PlatformTransactionmanager是一个接口,所以可以很容易的复制。不像JNDI一样与查找策略耦合。PlatformTransactionManager实现像其他在Spring框架IOC容器里定义一样。这个好处让Spring框架事务值得抽象,即使使用JTA。如果直接使用JTA,事务代码测试更简单。

再一次复合Spring的理念,TransactionException可以被任何PlatformTransactionManager未检查的接口方法抛出(因为继承了java.lang.RuntimeException类)。事务基础失败几乎总是致命的。极少情况下,应用代码可以真正从事务失败里恢复,应用开发者可以选择捕获并处理TransactionException。注意的点是开发者不必须这么做。

getTransaction(..)方法返回一个TransactionStatus对象,依赖于TransactionDefinition参数。返回的TransactionStatus可能是一个新事务,或者如果当前调用栈匹配一个存在的事务会返回已存在的。这暗示后面的情况是,作为Java EE事务的上下文,TransactionStatus与执行的线程相关联。

TransactionDefinition接口定义:

  • Isolation: 事务与其他事务隔离的程度。比如,这个事务能看到其他事务未提交的写吗?
  • Propagation: 通常地,在一个事务范围内所有代码的执行都会在那个事务里运行。但是,有一个选项当在事务上下文已经存在的时候,事务执行方法的事件指定行为。比如,代码可以继续运行在存在的事务里(通用情况);或者存在的事务可以被暂停然后创建新的事务。Spring提供了所有的事务范围选项,和EJB CMT相似。在Spring里查看相关的事务传播(propagation)语义,查看章节16.5.7,Transaction propagation
  • Timeout: 事务多久会超时,并且自动回归通过底层事务基础设施
  • Read-only status: 只读事务是在只读但不修改的时候可用。只读事务在在很多场景里是很有用的优化,比如使用Hibernate的时候。

这些设置反映了标准的事务概念。如果需要,参考资源,讨论事务隔离等级和其他核心事务概念。理解这些概念对使用Spring框架或者任何事务管理解决方法很重要。

TransactionStatus接口为事务代码提供了简单的方式控制事务执行和查询事务状态。这概念应该很熟悉,因为他们和所有的事务APIs是共同的:

public interface TransactionStatus extends SavepointManager {
    boolean isNewTransaction();
    boolean hasSavepoint();
    void setRollbackOnly();
    boolean isRollbackOnly();
    void flush();
    boolean isCompleted();
}

无论你在Spring里选择的是声明式还是编程式事务管理,正确的定义PlatformTransactionManager实现是至关重要的。通常通过依赖注入定义这个实现。

PlatformTransactionManager实现一般需要了解他们工作环境的知识:JDBC、JTA、Hibernate等等。下面的例子告诉你如何定义本地的PlatformTransactionmanager实现。(下面是普通的JDBC例子)

定义一个JDBCDataSource

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
    <property name="driverClassName" value="${jdbc.driverClassName}" />
    <property name="url" value="${jdbc.url}" />
    <property name="username" value="${jdbc.username}" />
    <property name="password" value="${jdbc.password}" />
</bean>

定义的相关的PlateformTransactionManagerbean会有一个对DataSource的引用。如下:

<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource" />
</bean>

如果在JavaEE容器里使用JTA,然后使用一个容器DataSource,通过JNDI,结合Spring的JtaTransactionManager。JTA和JNDI查找版本如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:jee="http://www.springframework.org/schema/jee" 
       xsi:schemaLocation=" 
                http://www.springframework.org/schema/beans 
                http://www.springframework.org/schema/beans/spring-beans.xsd 
                http://www.springframework.org/schema/jee 
                http://www.springframework.org/schema/jee/spring-jee.xsd"> 

    <jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/> 

    <bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" /> 

    <!-- other <bean/> definitions here -->
</beans>

JtaTransactionManager无需知道DataSource,或者任何其他特定的资源,因为它使用容器的全局事务管理基础设施。

上面定义的dataSourcebean使用<jndi-lookup/>标签来自于jee命名空间。查看Chapter40, XML Schema-based configuration了解基于schema配置的信息,从Section40.2.3,"the jee schema"了解更多关于<jee/>标签的信息。

也可以很容易的使用Hibernate的本地事务,如下面的例子所示。这个例子里,需要定义一个Hibernate的LocalSessionFactoryBean,是应用代码会从Hibernate获取的session实例。

DataSourcebean定义会和之前局部JDBC例子相似,所以这里就不展示了。

如果DataSource使用的是非JTA事务管理,但是是通过JNDI查找和Java EE容器管理的,那么他也应该不是无事务的,因为是Spring框架,而不是JavaEE容器管理事务

这里例子里的txManagerbean是HibernateTransactionManager类型。相同的DataSourceTransactionmanager需要引入DataSourceHibernateTransactionManager需要引入SessionFactory.

<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"> 
    <property name="dataSource" ref="dataSource"/> 
    <property name="mappingResources> 
      <list> 
        <value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value> 
      </list> 
    </property> 
    <property name="hibernateProperties"> 
        <value> hibernate.dialect=${hibernate.dialect} </value> 
    </property>
</bean>

<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"> 
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

如果你使用Hibernate和Java EE容器管理JTA事务,那么你应该使用相同的JtaTransactionManager给JDBC

<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>

如果使用JTA,那么你的事务管理定义会查找相同的,不管你使用的是什么数据访问技术,JDBC,Hibernate JPA或者任何其他支持的技术。这是因为JTA事务是全局的事务,可以支持任何事务源

这些例子里,应用代码不需要任何改变。只需要改变配置就能改变事务如何被管理的,即使是从局部变为全局或者全局改为局部。

4. 事务同步资源

现在你应该清楚了如何创建不同的事务管理,和如何关联资源需要同步的事务(比如DataSourceTransactionManager到JDBCDataSource,HibernateTransactionManager到Hibernate的SessionFactory,等等),这章描述了应用代码如何直接或者间接使用比如JDBC、Hibernate或者JDO的持久API,保证资源被创建,重复使用和正确的清理。这章同样讨论事务同步如何通过相关的PalteformTransactionManager被触发(可选)

1.高级的同步方法

更受欢迎的方法是使用Spring的最高等级的模板,基于持续集成APIs或者使用原生的ORM APIs transaction- aware工厂bean或者代理管理原生资源工厂。这些transaction-aware解决方法内部的处理资源的创建和重复使用,清理,资源的可选事务同步和异常映射。因此用户数据访问代码不需要解决这些任务,但是可以纯粹关注非boilerplate持久逻辑。一般的,使用原生ORM API或者通过使用JdbcTemplate访问JDBC的模板方法。

2.低级同步方法

类比如DataSourceUtils(对JDBC),EntityManagerFactoryUtils(对JPA),SessionFactoryUtils(对Hibernate),PersistenceManagerFactoryUtils(对JDO),等等处于较低的水平。如果希望应用代码直接处理原生持久APIs的资源类型,使用这些类来保证正确的获取Spring Framework-managed 实例,事务是(可选的)同步,并且在这个过程发生的异常会正确的映射到一致的API。

比如,在JDBC的列子里,而不是传统的JDBC在DataSource上调用getConnection()方法的方式,不是像下面这样使用Spring的org.springframework.jdbc.datasource.DataSourceUtils类:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果已经有一个同步连接到已存在的事务,那么这个实例会被返回。否则,方法调用触发器创建一个新的连接,是(可选)同步到任何存在的事务,并且后续的也可以在相同的事务上可用。已经提到过,任何SQLException是包含在Spring框架CannotGetJdbcConnectionException里,一个Spring框架的未检查的DataAccessExceptions结构。这种方式给你很多信息,可以很容易的从SQLException里获取到,并确保跨越数据库的可移植性,甚至在不同的持久化技术。

这种方式当然也在没有Spring事务管理可以用(事务同步是可选的),所以不管有没有使用Spring事务管理,你都可以使用。

当然,一旦你使用了Spring的JDBC支持,JPA支持或者Hibernate支持,你就不会想使用DataSourceUtils或者其他helper类,因为通过Spring抽象比直接使用相关的APIs更爽。比如,如果使用SpringJdbcTemplate或者jdbc.object包去简化JDBC的使用,正确的在幕后获取链接,并且不用写任何特殊的代码。

3.TransactionAwareDataSourceProxy

在最低等级存在着TransactionAwareDataSourceProxy类。这是目标DataSource的代理,包含目标DataSource去添加awareness of Spring-managed transactions。这么说来,有点像JavaEE Server提供的事务JNDIDataSource

应该几乎不需要或者想使用这个类,除非已有的代码必须得调用并且传递一个标砖的JDBCDataSource接口实现。这个例子里,这个代码可能是可用的,但是参与Spring管理事务。更适合的是使用上面提到的高级抽象写新代码。

声明式事务管理

大多数Spring框架用户选择声明式事务管理。这个选择对应用代码的影响很小,因此是最符合理想的非侵入性的轻量级容器。

Spring框架的声明式事务管理让和Spring切面变成成为可能,尽管,作为事务切面代码附带Spring框架分布,所以可能在boilerplate格式里使用,一般使用这个代码不需要了解AOP理念。

Spring框架的声明式事务管理和EJB CMT相似,可以指定事务行为(或者缺乏)低到单一的方法等级。如果需要在事务上下文调用setRollbackOnly()是可能的。两种事务管理的不同之处是:

  • 不像EJB CMT,绑定到JTA,Spring框架的声明式事务管理可以在任何环境里运行。它可以通过简单适合的配置文件就可以使用JDBC、JPA、Hibernate或者JDO与JTA事务或者局部事务运行。
  • 可以将Spring框架声明式事务管理应用到任何类,不只是像EJBs只有特定的类。
  • Spring框架提供了声明式回滚规则,一个EJB里没有相同的特性。编程式和声明式都支持回归规则。
  • 通过使用AOP,Spring框架可以自定义事务行为。比如,可以在事务回滚里插入自定义行为。也可以添加任意的advie,和事务advice。但EJB CMT,你不能影响到容器的事务管理,除非有setRollbackOnly()
  • Spring框架不支持跨越远程调用的食物上下文传播,就如高端应用服务器。如果需要这个特性,我们建议你使用EJB。但是,使用这个特性前谨慎考虑,因为正常的,不需要跨越远程事务调用。
TransactionProxyFactoryBean在哪?
Spring2.0版本里的声明式事务与以上版本有很大的不同,主要的区别是再也不需要配置``TransactionProxyFactoryBean``。

前Spring2.0的配置依然是100%有效的配置;考虑一下新的``<tx:tags />``作为简化定义``TransactionProxyFactoryBean``

回归规则的概念很重要:他们是你指定哪个异常(和可抛出)应该引起回滚。在配置里定义这个声明,而不是在Java代码里。所以,尽管你依然可以在TransactionStatus对象里调用setRollbackOnly()来回滚当前的事务,通常你可以指定一个规则,MyApplicationException必须总是导致回滚。明显的有点就是业务对象不再依赖于事务基础设施。比如,他们一般不需要导入Spring事务APIs,或者其他Spring APIs。

尽管EJB容器在系统异常上默认行为是自动回滚(经常是一个运行时异常),EJB CMT不会在应用异常(除了java.rmi.RemoteException的检查异常)上自动回滚事务。Spring的声明式事务管理默认行为参照EJB行为(只有在未经检查的异常上进行自动回滚),这个对自定义很有帮助。

1. 理解Spring框架的声明式事务实现

简单的告诉你在类上使用@Transactional注解和添加@EnableTransactionManagement到配置上还不足够,还期望你能知道它是如何工作的。本章节解释Spring框架的声明式基础设施的内部工作在事务相关问题的事件里。

掌握Spring框架的声明式事务支持最关键的概念是这个支持是通过AOP proxies启用的,并且事务advice是通过元数据(目前是XML或者基于注解)驱动的。结合AOP事务元数据产生一个AOP代理,一个使用TransactionInterceptor结合合适的PlatformTransactionManager实现驱动事务around method调用。

理论上,在一个事务代理上调用方法看起来是这样的:

Paste_Image.png
2.声明式事务实现例子

考虑到下面的接口和它的实现。这个例子使用FooBar类作为替代,所以你可以几种在事务使用用例上而不用关心特定的主要model。对于本示例,事实上DefaultFooService在每个实现方法体内类抛出UnsupportedOperationException实例是好的。它让你看到事务创建和遇到UnsupportedOperationException回滚。

// the service interface that we want to make transactional

package x.y.service;

public interface FooService { 

    Foo getFoo(String fooName); 

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo); 

    void updateFoo(Foo foo);

}
// an implementation of the above interface
package x.y.service;

public class DefaultFooService implements FooService { 

    public Foo getFoo(String fooName) { 
        throw new UnsupportedOperationException(); 
    } 

    public Foo getFoo(String fooName, String barName) { 
        throw new UnsupportedOperationException(); 
    } 

    public void insertFoo(Foo foo) { 
        throw new UnsupportedOperationException(); 
    } 

    public void updateFoo(Foo foo) { 
        throw new UnsupportedOperationException(); 
    }
}

假设FooService实例的前两个方法,getFoo(String)getFoo(String, String)必须执行只读语义的事务上下文,并且是另一个方法,insertFoo(Foo)updateFoo(Foo)必须执行读写语义的事务上下文。下面的配置会在后面段落里解释:

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation=" 
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd"> 

    <!-- this is the service object that we want to make transactional --> 
    <bean id="fooService" class="x.y.service.DefaultFooService"/> 

    <!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) --> 
    <tx:advice id="txAdvice" transaction-manager="txManager"> 
        <!-- the transactional semantics... --> 
        <tx:attributes> 
            <!-- all methods starting with 'get' are read-only --> 
            <tx:method name="get*" read-only="true"/> 
            <!-- other methods use the default transaction settings (see below) --> 
            <tx:method name="*"/> 
        </tx:attributes> 
    </tx:advice> 

    <!-- ensure that the above transactional advice runs for any execution of an operation defined by the FooService interface --> 
    <aop:config> 
        <aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/> 
        <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/> 
    </aop:config> 

    <!-- don't forget the DataSource --> 
    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> 
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/> 
        <property name="username" value="scott"/> 
        <property name="password" value="tiger"/> 
    </bean> 

    <!-- similarly, don't forget the PlatformTransactionManager --> 
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
        <property name="dataSource" ref="dataSource"/> 
    </bean> 

    <!-- other <bean/> definitions here --></beans>

检查上面的配置。你想创建一个service对象,fooServicebean,事务性的。事务语义封装到了<tx:advice/>定义里。<tx:advice/>定义理解为:"所有以'get'开头的方式都执行在只读事务里,其他所有方法默认是事务语义"。这个例子里,<tx:advice/>标签的transaction-manager属性txManager设置了PlatformTransactionMangerbean来驱动事务。

如果你想装配的PlatformTransactionManager的名字是transactionManager,那么<tx:advice/>可以忽略掉transaction-manager属性。但如果是其他的名字,就必须显示的设置transaction-manger

<aop:config/>定义保证通过txAdvicebean定义的事务advice在合适的点执行。首先在FooService接口(fooServiceOperation)里定义了一个匹配任何操作的execution的切入点(pointcut),然后使用一个advisor关联pointcut和txAdvice。结果表明在fooServiceOperation的执行点,txAdvice``定义的advice就会执行。

<aop:pointcut/>节点定义的表达式是一个AspectJ pointcut表达式。查看Chapter 10, Aspect Oriented Programming with Spring获取更多信息。

一个通用的需求是让整个service层都是事务性的。最好的方式就是简单的改变pointcut表达式匹配任何service层的操作。比如:

<aop:config>
    <aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods" />
</aop:conifg>

现在我们已经分析了配置,你可能会问自己:“好吧...但是着所有的配置实际上做啥的”

上面的配置将会被用来围绕fooServicebean定义创建一个事务性的代理。代理会配置事务性的advice,所以当一个合适的方法在代理上被调用,一个事务就被启动,暂停,标记为只读,等等。取决于事务配置关联的方法。下面的程序是测试上面的配置:

public final class Boot {
    public static void main(final String[] args) throws Exception {
        ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml", Boot.class);
        FooService fooService = (FooService) ctx.getBean("fooService");
        fooService.insertFoo(new Foo());
    }
}

上面的代码执行大概看起来是这样的:(Log4J输出和堆栈跟踪来自于insertFoo()方法抛出的UnsupportedOperationException)

<!-- the Spring container is starting up... -->
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors

<!-- the DefaultFooService is actually proxied -->[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]

<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo

<!-- the transactional advice kicks in here... -->
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction

<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]

<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
[DataSourceUtils] - Returning JDBC Connection to DataSource

Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
<!-- AOP infrastructure stack trace elements removed for clarity -->
at $Proxy0.insertFoo(Unknown Source)
at Boot.main(Boot.java:11)
3.回归声明式事务

上一章概述了如何指定事务设置类的基础知识,一般的service层类,声明式的在应用中。这章描述了你如何在一个简单的声明式回滚事务。

推荐的方式是指示Spring框架的事务基础是在当前已存在事务上下文里抛出一个Exception来回滚事务。Spring框的事务基础代码会捕获任何未处理的Exception冒泡到调用栈,然后做决定是否标记为回滚

在它的默认配置里,Spring框架的事务基础代码仅仅在运行时、未检查的异常为事务回滚;就是,抛出的是一个异常的实例或者是RuntimeException的子类。(Errors默认的也会导致回滚)。默认配置里从事务方法里抛出的已检查的异常不会导致回滚。

可以准确的配置哪种类型的异常会被回滚,包括已检查的异常。下面的XML片段延时了如何配置一个检查的异常导致回滚,特定的应用Exception类下那个。

<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:methods name="get*" read-only="true" rollback-for="NoProductInStockException" />
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>

如果一个异常抛出的时候不想回滚事务,也可以指定‘不回滚规则’。下面的例子告诉Spring框架的事务基础去提交事务即使有未处理的InstrumentNotFoundException

<tx:advice id="txAdvice">
    <tx:attributes>
        <tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException" />
        <tx:method name="*" />
    </tx:attributes>
</tx:advice>

当Spring框架的事务基础捕获了异常然后检查回滚规则决定是否对是否标记回滚,会匹配the strongest规则。所以下面例子的配置,任何异常除了InstrumentNotFoundException会导致事务的回滚。

<tx:advice id="txAdvice">
    <tx:attributes>
        <tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException" />
    </tx:attributes>
</tx:advice>

也可以指定一个需要编程式的回滚。尽管非常简单,这个方式具有侵入性,并且和代码高度耦合:

public void resolvePosition(){
    try{
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

尽可能的鼓励使用声明式的方法。编程式回滚应该只在绝对需要的时候才使用,但是她的使用文件面对的是干净的基于POJO架构的实现。

4.为不同的beans配置不同的事务方式

考虑可能有很多个service层对象,并且想对每一个应用一个完全不同的事务配置。可以通过定义不同的<aop:advisor/>节点和不同的pointcutadvice-ref节点值。

作为点的比较,首先假如所有的service层类都定义在x.y.service包里。为了让所有的定义在这个包或者这个子包里的类的实例bean,并且以Service结尾的有默认的事务配置,配置可能如下:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config> 
        <aop:pointcut id="serviceOperation" expression="execution(* x.y.service..*Service.*(..))"/> 
        <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/> 
    </aop:config>

    <!-- these two beans will be transactional... --> 
    <bean id="fooService" class="x.y.service.DefaultFooService"/> 
    <bean id="barService" class="x.y.service.extras.SimpleBarService"/> 
    <!-- ... and these two beans won't --> 
    <bean id="anotherService" class="org.xyz.SomeService"/> 
    <!-- (not in the right package) --> 
    <bean id="barManager" class="x.y.service.SimpleBarManager"/> 
    <!-- (doesn't end in 'Service') --> 
    <tx:advice id="txAdvice"> 
        <tx:attributes> 
            <tx:method name="get*" read-only="true"/> 
            <tx:method name="*"/> 
        </tx:attributes> 
    </tx:advice> 

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>

下面的例子演示了如何配置两个不同bean的完全不同的事务设置。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd">

    <aop:config> 
        <aop:pointcut id="defaultServiceOperation" expression="execution(* x.y.service.*Service.*(..))"/> 
        <aop:pointcut id="noTxServiceOperation" expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/> 
        <aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/> 
        <aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
    </aop:config>

    <!-- these two beans will be transactional... --> 
    <bean id="fooService" class="x.y.service.DefaultFooService"/> 

    <!-- this bean will also be transactional, but with totally different transactional settings --> 
    <bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/> 

    <tx:advice id="defaultTxAdvice"> 
        <tx:attributes> 
            <tx:method name="get*" read-only="true"/> 
            <tx:method name="*"/> 
    </tx:attributes> 
    </tx:advice> 

    <tx:advice id="noTxAdvice"> 
        <tx:attributes> 
            <tx:method name="*" propagation="NEVER"/> 
        </tx:attributes> 
    </tx:advice>

    <!-- other transaction infrastructure beans such as a PlatformTransactionManager omitted... -->

</beans>
5.<tx:advice/>设置

本章节总结各种可以在<tx:advice/>节点上事务设置。默认的<tx:advice/>如下:

  • Propagation setting is REQUIRED.
  • Isolation level is DEFAULT.
  • Transaction is read/write
  • Transaction timeout默认是基于的事务系统的,如果不支持那就没有
  • 任何RuntimeException触发回滚,并且任何检查的Exception不会触发。

可以改变默认设置,嵌套在<tx:advice/><tx:attributes/>标签的各种<tx:method/>属性总结如下:

|属性|必须|默认值|描述
|-
|name|是||与事务属性关联的方法名。通配符(*)字符可以用于关联相同的事务属性设置的方法,比如:get*,handle*on*Event等等
|propagation|No|REQUIRED|事务传播行为
|isolation|No|DEFAULT|事务隔离等级
|timeout|No|-1|事务超时时间(秒)
|read-only|No|false|事务是只读的
|rollback-for|No||触发回滚的Exceptions(s),逗号分隔,比如com.foo.MyBusinessException,ServletException``` |no-rollback-for|No||不触发回滚的Exception(s)``;逗号分隔

6 使用@Transactional

除了基于XML的声明式方法实物配置,也可以使用基于注解的方式。声明式事务语义直接在java源代码里,里受影响的代码更近。这不像高度耦合那样危险,因为因为代码不管如何部署,这个代码都是事务性的。

标准的javax.transaction.Transactional注解同样支持作为Spring自己的注解的替代。请参考JTA1.2文档获取更多信息。

使用@Transactional注解提供的易用性最好提供一个例子,如下所示:

// 这个service类我们想让他是事务性的
@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);
    
    Foo getFoo(String fooName, String barName);
  
    void insertFoo(Foo foo);

    void updateFoo(Foo foo);
}

当上面的POJO作为bean在Spring IOC容器里定义时,这个bean实例只需要一行XML配置就能是事务性的

<!-- from the file 'context.xml' -->
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation=" 
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd"> 

    <!-- this is the service object that we want to make transactional --> 
    <bean id="fooService" class="x.y.service.DefaultFooService"/> 

    <!-- enable the configuration of transactional behavior based on annotations --> *
    <tx:annotation-driven transaction-manager="txManager"/>*<!-- a PlatformTransactionManager is still required --> 
    
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
        <!-- (this dependency is defined somewhere else) --> 
        <property name="dataSource" ref="dataSource"/> 
    </bean> 

    <!-- other <bean/> definitions here -->
</beans>

如果使用的是基于Java的配置,那么使用@EnableTransactionManagement注解提供相同的支持。简单的添加一个@Configuration类。

方法可见性和@Transactional
当使用代理时,应用``@Transactional``注解仅仅是对*public*可见。如果注解在protected,private或者package-visible方法上注解``@Transactional``注解,不会有错误,但是注解的方法不会有事务性设置。如果想在非public方法上,考虑使用AspectJ

可以将@Transactional注解放在接口定义,接口上的方法,类定义,或者一个类上的public方法上。但是,仅仅存在@Transactional注解是不够激活事务的特性的。@Transactional注解是简单的可以使用的元数据,通过一些是可以配置合适的有着事务行为的bean的运行时基础@Transactional-aware注解。前面的例子里,<tx:annotation-driven/>节点切换到事务性行为。

Spring建议把@Transaction注解只对具体的类(具体类上的方法)注解,而不是注解接口。当然是可以把@Transactional放在接口或者接口的方法上,但只会在你想使用基于接口代理的。事实上Java注解不继承自接口意味着你使用的是基于类的代理(proxy-target-class="true")或者weaving-based aspect(mode="aspectj")。然后事务设置没有被代理或者weaving infrastructure标识,并且对象没有在事务性代理里。

默认的代理模式里,只有外部通过dialing调用的才会被拦截。实际上,这意味着,目标对象里的方法调用另一个方法,在运行时不会产生真正的事务性即使调用方法被注解为@Transactional。同时,代理必须完全初始化提供预期的行为,所以不应该在初始化代码里依赖这个特性。比如@PostConstruct.

考虑AspectJ模式的使用(在下面表格查看更多),如果预期self-invocations也有事务性。这个例子,不会在第一时间代理,而是,目标类会被weaved(就是,它的byte code会被修改)为了转换@Transactional到运行时行为或者任何方法

| XML属性|注解属性|默认|描述
|-
|transaction-manager|N/A(查看TransactionManagementConfigurerjavadocs)|transactionManager|事务管理使用的名字。仅仅在名字不是transactionManager是需要填写。
|mode|mode|proxy|默认模式proxy处理注解bean使用Spring的AOP框架代理(理解proxy语义,如上讨论,只会通过代理调用才会应用)。而另一个模式aspectjweaves 受影响的类,随着Spring的AspectJ事务aspect,修改目标类byte code应用到任何类型的方法调用。AspectJ weaving需要spring-aspects.jar在类路径里并且在load-time weaving(或者compile-time weaving)启用。(查看the section called "Spring configuration"了解如何设置load-time weaving)
|proxy-target-class|proxyTargetClass|false|只对proxy模式有效。控制有@Transactional注解的事务代理类型的创建。如果proxy-target-class属性设置为true,那么基于类代理被创建。如果proxy-target-classfalse或者如果属性被忽略了,那么标准的JDK基于接口代理会被创建(查看section10.6, "Proxying mechanisms"获取更多不同代理类型检查的详情)
|order|order|Ordered.LOWESt_PRECEDNCE|定义应用到被@Transactional注解的事务advice优先级(从the section called "Advice ordering"获取更多关于AOP advice的建议)。没有指定优先级意味着AOP子系统决定advice的优先级

@EnableTransactionManager<tx:annotation-driven/>只查找在相同的application context里定义的有@Transactional的bean。这意味着,如果将注解驱动配置在为DispatcherServletWebApplicationContext里,就只会检查controllers的@Transactional,而不是services。查看Section21.2, "The DispatcherServlet"获取更多信息

当一个方法计算是事务性设置是,the most derived location 获得优先级。下面的例子中,DefaultFooService类在类等级的注解设置为只读事务。但是设置在updateFoo(Foo)方法上的会优于类上的transactional设置。

@Transactional(readOnly = true)
public class DefaultFooService implements FooService {

    public Foo getFoo(String fooName) {
        // do something
    }

    // 这个方法的设置有优先权
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}

@Transactional设置
@Transactional注解是指定接口、类或者方法必须有事务性语义的元数据。比如,“方法调用时启动一个全新的只读事务,暂停任何已存在的事务”。默认的@Transactional设置如下:

  • Propagation设置是PROPAGATION_REQUIRED
  • Isolation等级是ISOLATION_DEFAULT.
  • 事务是读/写
  • Transaction timeout 默认是底层事务系统的默认timeout,如果不支持则没有timeout
  • 任何RuntimeException触发回滚,并且任何检查过的Exception不会。

这些默认的设置都可以修改,@Transactional注解属性总结如下:

|属性|类型|描述
|-
|value|String|可选的限定符指定使用的事务管理
|propagation|enum:Propagation|可选的propagation设置
|isolation|enum:Isolation|可选的隔离等级
|readOnly|boolean|Read/write vs. read-only transaction
|timeout|int(单位:秒)|事务超时
|roolbackFor|派生自ThrowableClass数组对象|必须要回滚的异常数组类
|rollbackForClassName|类名数组,派生自Throwable|必须要回滚的异常类
|noRollbackFor|派生自Throwable的类数组对象|不回滚的异常
|noRollbackForClassName|字符类型的类名数组,派生自Throwable|不回滚的异常

目前,不能显式的控制事务的名字,name的意思是事务的名字会显式在事务监控里。如果适合的(比如,WebLogic的事务监控),并且在日志输出里。对于声明式事务,,事务名总是权限定类名+"."+transactinally-advised类的方法名。比如,如果BusinessServicehandlePayment(..)方法开始一个事务,事务的名字就会使com.foo.BusinessService.handlePayment

使用@Transactinal多个事务管理
大多数Spring应用只需要一个事务管理,但是有些场景需要多个独立的事务管理。@Transactional注解的value属性可以用于PlatformTransactionManager指定的身份去使用。这也可以是bean name或者事务管理bean的限定符值。比如,使用限定符号,下面的java代码

public class TransactionalService {

    @Transactional("order")
    public void setSomething(String name) { ... }

    @Transactional("account")
    public void doSomething() { ... }
}

可以和application context里的事务管理bean定义结合:

<tx:annotation-driven/>

<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"
    ...
    <qualifier value="order" />
</bean>

<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    ...
    <qualifier value="account" />
</bean>

这个情况下,TransactionalService上的两个方法会在独立的事务管理下运行,分别是"order"和"account"限定符。如果没有特定的限定的PlatformTransactionManager bean,依然会使用默认的<tx:annotation-driven>目标bean 名transacationManager

自定义快捷注解

如果你发现在同事使用相同的@Transactional属性在不同的方法上,那么Spring's meta-annotation support允许自定义快捷注解。比如:

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("order")
public @Interface OrderTx{
}

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Transactional("account")
public @interface AccountTx {
}

上面的例子就可以是

public class TransactionalService {

    @OrderTx
    public void setSomething(String name) { ... }

    @AccountTx
    public void doSomething() { ... }
}

这里我们使用了事务管理的限定符语法,当然我们也可以包含propagation行为,rollback rules,timeout等等。

7. Transaction propagation

本章节在Spring里描述了一些transaction propagation的语义。请记住本章节不是介绍适当的transaction propagation;而是在Spring里关于transaction propagation的语义细节。

在Spring管理的事务里,注意physicallogical事务的不同之处,并且propagation设置如何应用这些不同。

PROPAGATION_REQUIRED

当propagation设置是PROPAGATION_REQUIRED,根据应用的设置会为每个方法创建一个逻辑性事务范围。每个这样的逻辑事务范围可以单独的决定rollback-only状态,与外部事务范围是逻辑上独立于内部事务范围。当然在标准的PROPAGATION_REQUIRED行为的例子里,所有这些的范围会被映射到相同的物理性事务。所以rollback-only标志设置在内部事务范围会影响外部事务的实际提交(actually commit)。

但是,一个内部事务范围设置了rollback-only标记,外部事务没有决定回滚本身,所以rollback(默默的由内部事务范围触发)是unexpected的。相应的UnexpectedRolbackException也会在那个时候被抛出。这是预期的行为,所以事务的调用者永远不会被误导认为提交不是的时候执行。所以如果内部事务(外部事务不知道的)默默的标记一个事务为rollaback-only,外部调用者仍然会调用提交。外部调用者需要接受一个UnexpectedRollbackException来清楚的指示回滚会被执行。

PROPAGATION_REQUIRES_NEW

PROPAGTION_REQUIRES_NEW,与PROPAGATION_REQUIRED相比,为每个受影响的事务范围使用了完全独立的事务。在这种情况下,底层物理事务是不同的,因此可以独立提交或者回滚,外部事物不受内部事务的回滚状态的影响。

Nested

PROPAGATION_NESTED使用单独的物理事务和多个可以回滚的保存点。这样部分的回滚允许内部事务范围为它的范围触发回滚,与外部事务可以继续物理事务,尽管一些操作已经被回滚。这个设置一般映射在JDBC保存点,所以只能用在JDBC的资源事务。查看Spring的DataSourceTransactionManager.

8 Advising transactional operations

假设你想执行事务和一些基础的profiling advice.如何在<tx:annotation-driven/>实现这个效果。

当你调用updateFoo(Foo)方法是,你想要看到下面的行为:

  • Configured profiling aspect starts up
  • Transactional advice executes
  • Method on the advised object executes
  • 事务提交(Transaction commits)
  • Profiling aspect reports exact duration of th ew

本章不解释AOP的各种(除了应用与事务的)。

这里上面讨论的简单的profiling aspect代码。advice的顺序是通过Ordered控制的。查看the section called "Advice ordeing查看详细的advice ordering.

package x.y;

import org.aspectj.lang.ProceedingJoinPoint;
import org.springframework.util.StopWatch;
import org.springframework.core.Ordered;

public class SimpleProfiler implements Ordered {

    private int order;

    // allows us to control the ordering of advice
    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
          this.order = order;
    }

    public Object profile(ProceedingJoinPoint call) throws Throwable {
          Object returnValue;
          StopWatch clock = new StopWatch(getClass().getName());
          try {
              clock.start(call.toShortString());
              returnValue = call.proceed();
          } finally {
              clock.stop();
              System.out.println(clock.prettyPrint());
          }
          return returnValue;
    }
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation=" 
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd"> 
     <bean id="fooService" class="x.y.service.DefaultFooService"/> 

    <!-- this is the aspect --> 
    <bean id="profiler" class="x.y.SimpleProfiler"> 
        <!-- execute before the transactional advice (hence the lower order number) --> 
        <property name="order" __value="1"__/> 
    </bean> 

    <tx:annotation-driven transaction-manager="txManager" __order="200"__/> 

    <aop:config> 
        <!-- this advice will execute around the transactional advice  --> 
        <aop:aspect id="profilingAspect" ref="profiler"> 
            <aop:pointcut id="serviceMethodWithReturnValue" 
                    expression="execution(!void x.y..*Service.*(..))"/> 
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/> 
        </aop:aspect> 
    </aop:config> 

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> 
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/> 
        <property name="username" value="scott"/> 
        <property name="password" value="tiger"/> 
    </bean> 

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
        <property name="dataSource" ref="dataSource"/> 
    </bean>

</beans>

上面的配置的结果是fooServicebean有profiling和事务切面按照想要的顺序应用的。你可以配置一个相同风格的切面

下面的实例和上面的是一样的效果,但是使用的是纯XML声明式方式

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
    xmlns:aop="http://www.springframework.org/schema/aop" 
    xmlns:tx="http://www.springframework.org/schema/tx" 
    xsi:schemaLocation=" 
        http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans.xsd 
        http://www.springframework.org/schema/tx 
        http://www.springframework.org/schema/tx/spring-tx.xsd 
        http://www.springframework.org/schema/aop 
        http://www.springframework.org/schema/aop/spring-aop.xsd"> 

    <bean id="fooService" class="x.y.service.DefaultFooService"/> 

    <!-- this is the aspect --> 
    <bean id="profiler" class="x.y.SimpleProfiler"> 
        <!-- execute before the transactional advice (hence the lower order number) --> 
        <property name="order" __value="1"__/> 
    </bean> 

    <tx:annotation-driven transaction-manager="txManager" __order="200"__/> 

    <aop:config> 
        <aop:pointcut id="serviceMethodWithReturnValue" 
                    expression="execution(!void x.y..*Service.*(..))"/> 

        <aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" __order="2__" />
        <aop:aspect id="profilingAspect" ref="profiler">
        <aop:pointcut id="serviceMethodWithReturnValue"
              expression="execution(!void x.y..*Service.*(..)" />
            <aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/> 
        </aop:aspect> 
    </aop:config> 

    <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> 
        <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/> 
        <property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/> 
        <property name="username" value="scott"/> 
        <property name="password" value="tiger"/> 
    </bean> 

    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> 
        <property name="dataSource" ref="dataSource"/> 
    </bean>

</beans>

上面的配置会让fooService有profiling和事务性切面按配置顺序应用到上面。如果想让profiling advice在事务性advice进入之后,但是在事务性advice出去之前执行。可以简单的交换profiling aspect bean的order属性值,让他高于transactional advice的order value。

9. 使用@Transactional with AspectJ

还可以使用Spring框架的@Transactional通过AspectJ aspect支持Spring容器之外的。达到这个目的,首先在类上(可选的类的方法上)使用@Transactional注解,然后连接(编织)应用于在spring-aspectes.jarorg.springframework.transaction.aspectj.AnnotationTransactionAspect。aspect必须配置一个transaction manager。当然可以使用Spring框架的IOC容器来管理依赖注入切面。最简单的方式是使用<tx:annotation-driven/>节点配置事务管理切面和给aspectj指定mode属性作为在Section 16.5.6 "Using @Transactional的描述。因为我们集中在Spring容器之外运行应用程序,我们将以编程的方式像你展示

// construct an appropriate transaction manager
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());

// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);

当使用这个切面,必须注解实现的类(和/或这个类里的方法),而不是任何这个类实现的接口。AspectJ遵从Java的规则:接口上的注解不会被继承

类上的@Transactional注解为这个类上的任何方法的执行指定默认的语义。

这个类里的方法上的@Transactional注解会重写类上面这个注解的默认事务语义。任何方法可能被注解,不管可见性。

为了用AnnotationTransactionAspect编织(weave)你的应用,必须同时使用AspectJ构建应用(查看AspectJ Development Guide)或者使用load-time weaving。查看section 10.8.4 "Load-time weaving with AspectJ in the Spring Framework

编程事务管理

Spring框架提供了两种编程式事务管理

  • 使用TransactionTemplate
  • 直接使用PlatformTransactionManager实现

Spring团队建议使用TransactionTemplate进行编程事务管理。第二种方式和使用JTAUserTransactionAPI相似,尽管异常处理更少麻烦。

1.使用 TransactionTemplate

TransacationTemplate使用了和其他Spring template相同的方式,比如JdbcTemplate。它使用 了回调方式,从不得不做样板式的获取和释放事务资源和代码意图驱动中解放,开发人员仅仅需要关注想要做什么。

将会在下面的例子看到,使用TransactionTemplate绝对耦合Spring的事务基础设施和APIs。不管编程式事务管理是否合适你的开发需要,不得不做这样的决定。

。。。不用编程式事务管理,所以不翻译了

选择编程式还是声明式事务管理?

编程式事务管理通常是一个好主意,仅当只有小数量的事务操作。比如,如果你的web应用只有修改操作需要事务,你可能不想使用Spring或者其他技术设置事务代理。这个案例里,使用TransactionTemplate就是一个好的方式。可以显示的设置事务的名字也只能使用编程方式完成事务管理。

另一方面,如果应用需要很多的事务操作,声明式事务是更好的选择。它让事务管理在业务逻辑之外,并且不难配置。当使用Spring框架,而不是EJB CMT,声明式事务管理将大大的减少配置成本。

事务绑定事件

从Spring4.2,event的listener可以绑定到事务的某个阶段。一般的例子是当事务成功完成的时候处理事件:这让使用events更加灵活,当 当前的事务的结果对listener很重要的时候。

通过@EventListener注解注册常规的事件监听器。如果需要绑定它到事务,使用@TransactionalEventListener。这么做的时候,监听器将会默认的绑定到事务提交的阶段。

让我们来看一个例子演示这个理念。假设一个组件发布一个order创建event并且我们想要定义一个应该只处理一旦这个事务作为提交成功的发布的监听器:

@Component
public class MyComponent {
      
      @TransactionalEventListener
       public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
           ...   
       }
}

@TransactionalEventListener注解暴露了一个phase属性运行自定义事务的哪个阶段应该绑定到listener。合法的阶段是BEFORE_COMMIT, AFTER_COMMIT(default),AFTER_ROLLBACKAFTER_COMPLETION聚合事务完成(提交或者回滚)。

如果没有事务运行,因为我们不能获取(提交前后,回滚前后)语义,所以监听器不会被调用。然而可以通过设置注解的fallbackExecution属性为true来重写默认的行为。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,050评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,538评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,673评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,622评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,047评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,974评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,129评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,893评论 0 209
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,654评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,828评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,297评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,619评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,326评论 3 243
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,176评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,975评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,118评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,909评论 2 278

推荐阅读更多精彩内容

  • Spring Boot 参考指南 介绍 转载自:https://www.gitbook.com/book/qbgb...
    毛宇鹏阅读 46,416评论 6 342
  • spring官方文档:http://docs.spring.io/spring/docs/current/spri...
    牛马风情阅读 1,582评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,111评论 18 139
  • 翻译自Spring官方文档 4.1.2版本 相关文章: Spring参考手册 1 Spring Framework...
    liycode阅读 485评论 0 4
  • 书,一个有灵魂高档商品。果真不同于如今的电子科技,用来用去都是同一个套路,厌倦了。同一价钱买来的书,其真正的用途在...
    xy78阅读 208评论 0 1