Spring 4 官方文档学习(九)数据访问之事务管理

说明:未整理版,未完待续,请绕行

本部分的重点是数据访问以及数据访问层与业务层之间的交互。

1、Spring框架的事务管理 介绍

http://docs.spring.io/spring/docs/current/spring-framework-reference/html/transaction.html

为什么要使用Spring事务管理?最有竞争力的原因是综合的事务支持!

Spring框架提供了一致的事务管理抽象,能够带来如下好处:

  1. 不同事务APIs, 相同的编程模型。不同的事务APIs:例如Java Transaction API (JTA)、JDBC、Hibernate、Java Persistence API (JPA)、以及Java Data Objects (JDO)。
  2. 支持声明式事务管理。
  3. 针对编码式事务管理的简化API -- 相对于复杂事务APIs来说,如JTA。
  4. 与Spring的数据访问抽象的高度集成。

 

下面的部分描述了Spring框架的事务增值(value-adds)和技术。(该章也包含了最佳实践、应用服务器集成 以及常见文件的解决方案的讨论。)

 

2、Spring框架事务支持模型的领先之处

传统上,Java EE 开发者有两个选择:全局的 或者 局部的 事务管理,二者都有其限制。

2.1、全局事务

全局事务可以让你使用跨越多事务的资源进行工作,典型的是关系型数据库和消息队列。应用服务器通过JTA管理全局事务。JTA 是一个臃肿的API(部分是因为它的异常模型)。更多的是,一个JTA UserTransaction 通常需要同JNDI种获取源,就是说你还需要使用JNDI。

显然,使用全部事务会限制应用代码的任何复用,因为JTA通常只在应用服务器环境中可用。

从前,推荐的方法是通过EJB CMT (Container Managed Transaction) 来使用全局事务,CMT是一种声明式事务管理。EJB CMT移除了事务有关的JNDI查找,虽然其自身对EJB的使用导致必须依赖JNDI。它移除了多数但不是全部对于Java代码的需求--以控制事务。最明显的负面是CMT被绑定到了JTA和应用服务器环境。EJB的负面太明显,所以不够吸引人。

2.2、本地事务

本地事务是资源专有的,例如关联到JDBC连接的事务。本地事务可能更容易使用,但有明显的缺点:它们可能无法在跨越多事务的资源中使用。例如,管理使用JDBC连接的事务的代码不能运行在一个全局JTA事务中。因为应用服务器没有被卷入事务管理,它无法帮助确认跨越多个资源的正确性。(必须指出,多数应用使用都在使用一个单独的事务资源)。另一个负面是本地事务对于编程模型来说是入侵性的。

2.3、Spring框架的一致的编程模型

Spring解决了全局的和本地的事务的缺点。它使得应用开发者们在任何环境中都可以使用一致的编程模型。一次编码,可以在不同的环境中从不同的事务管理策略受益。Spring框架提供了声明式和编码式的事务管理。多数用户倾向于声明式事务管理--多数情节下也是我们推荐的。

使用编码式事务管理时,开发者们使用Spring框架的事务抽象 可以运行在任何底层的事务设施之上。使用声明式事务管理,开发者们通常只编写少量甚至没有的事务代码,所以不需要依赖于Spring框架的事务API,或者任何其他事务API。

 

你需要一个事务管理的应用服务器吗?

Spring框架的事务管理支持改变传统的规则 -- 一个企业级Java应用需要一个应用服务器。

特别的是,你不需要为EJB的声明式事务准备一个应用服务器。事实上,即使你的应用服务器拥有强大的JTA能力,你仍然可能认为Spring框架的声明式事务提供了更强大和更具备生产力的编程模型。

典型的,只有你的应用需要处理跨越多个资源的事务时(很多应用没有这样的需求),你才需要一个应用服务器的JTA能力。许多高端应用使用一个单一的、高度可伸缩的数据库(例如Oracle RAC)来代替。独立的事务管理器如Atomikos Transactions 和 JOTM 是别的选择。当然,你可能需要其他应用服务器能力,例如JMS和Java EE Connector Architecture(JCA)。

 

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是一个接口,它可以很简单的被mocked或stubbed。它没有绑到一个查找策略(例如JNDI)。PlatformTransactionManager实现如同Spring框架的IoC容器中的所有其他对象(或bean)一样被定义。这使得Spring框架事务成为一个很好的抽象,即便你使用JTA。事务代码可以很简单的被测试。

 

Spring的哲学,PlatformTransactionManager接口的任意方法抛出的TransactionException都是unchecked(就是说,继承了java.lang.RuntimeException)。

getTransaction(..)方法返回一个TransactionStatus对象,依赖于一个TransactionDefinition参数。返回的TransactionStatus可能代表一个新的事务,或者可能代表一个已有的事务--如果匹配的事务存在于当前的call stack中。后一种情况,当在Java EE 事务上下文中时,一个TransactionStatus是关联到执行的一个线程。

 

TransactionDefinition接口指出了:

Isolation : 事务与其他事务的工作相隔离的度量。例如,这个事务能否看见其他事务的未提交的写入?

Propagation : 典型的情况是,在一个事务范围内执行的所有代码都会在该事务内执行。【感觉是废话?】然而,。。。 例如,代码可以继续运行在现有的事务中(通常情况);或者,现有事务可以被挂起,一个新的事务被创建。Spring提供了类似EJB CMT的所有事务propagation选项。后面有说。

Timeout : 一个事务可以在超时和自动回滚前运行多久。

Read-only status : 当你的代码只读取而不修改数据时,可以使用只读的事务。

 

这些设置折射了标准事务的概念。如果有必要,查阅事务隔离级别和其他事务概念的相关资料。理解这些概念是使用Spring框架或任何事务管理器解决方案的基础。

TransactionStatus接口提供了一个简单的方式以编码控制事务的执行和查询事务状态。

public interface TransactionStatus extends SavepointManager {

    boolean isNewTransaction();

    boolean hasSavepoint();

    void setRollbackOnly();

    boolean isRollbackOnly();

    void flush();

    boolean isCompleted();

}

在Spring中,无论你倾向于声明式还是编码式事务管理,定义正确的PlatformTransactionManager实现是绝对必须的。一般,你可以通过依赖注入来定义该实现。

PlatformTransactionManager实现通常要求其工作的环境的知识,如JDBC、JTA、Hibernate以及其他。见下例(简单JDBC):

<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>
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

 

如果你在一个Java EE容器中使用JTA,请使用一个容器DataSource -- 通过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,或者其他特定的资源,因为它使用容器的全局事务管理设施。

另外,以后会讲到<jee:jndi-lookup/>。

 

你也可以使用Hibernate的本地事务,很简单,见下面的例子。下面的例子,只需要定义一个Hibernate的LocalSessionFactoryBean,用于获取Hibernate session对象。

另外,DataSource的定义类似于上面的本地JDBC例子,略。

If the DataSource, used by any non-JTA transaction manager, is looked up via JNDI and managed by a Java EE container, then it should be non-transactional because the Spring Framework, rather than the Java EE container, will manage the transactions.

<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事务,应该和前面的(使用JDBC的JTA)例子使用相同的JtaTransactionManager:

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

所有的cases,应用代码都不需要改变!仅仅修改配置即可。

 

4、使用事务同步资源

本部分描述了应用代码如何直接或间接的使用持久层API如JDBC、Hibernate、或者JDO,来确保资源被正确的创建、复用已经清理。还讨论了事务同步是怎么通过相关的PlatformTransactionManager引发的 (可选)?

4.1、高级别同步

推荐使用Spring的基于持久层集成APIs的最高级模板,或者使用transaction-aware的工厂bean或管理native资源工厂的代理 的 native ORM APIs。 -- 真尼玛绕!!!

这些transaction-aware的解决方案内部处理资源的创建和复用以及清理、资源的可选的事务同步和异常应用。因此,用户数据访问代码不需要做这些任务,集中于单纯的逻辑即可。一般,使用native ORM API或者使用JdbcTemplate来处理JDBC访问。后面详述。

 

4.2、低级别同步

诸如DataSourceUtils(JDBC)、EntityManagerFactoryUtils(JPA)、SessionFactoryUtils(Hibernate)、PersistenceManagerFactoryUtils(JDO)此类的类,都是低级别的。当你想使用代码直接处理native persistence APIs 的资源类型时,可以使用这些类来确保获取合适的Spring框架管理的实例、同步事务(可选)、以及正确的映射异常到相关的API中。

 

例如,JDBC时,你可以使用org.springframework.jdbc.datasource.DataSourceUtils:

Connection conn = DataSourceUtils.getConnection(dataSource);

如果现有的事务已经有一个连接同步连接到它,那个实例会被返回。否则,该方法会创建一个新的连接--可能同步连接到任何已有的事务,让后续的复用可以用于相同的事务。SQLException是被封装在Spring框架的CannotGetJdbcConnectionException中,unchecked。会给出更多信息。

 

没有Spring事务管理时也能工作--就是说事务管理是可选的!

 

当然,一旦你使用了Spring的JDBC支持、JPA支持或者Hibernate支持,你会倾向于不使用DataSourceUtils或者其他帮助类,因为使用Spring抽象更愉悦。例如,如果使用Spring JdbcTemplate 或者 jdbc.object包来简化JDBC的使用,不必纠结于连接的处理。

 

4.3、TransactionAwareDataSourceProxy

最低级别!!!绝大多数情况下不建议使用它。

 

5、声明式事务管理

Spring框架的声明式事务管理使用Spring AOP实现,但你不需要理解AOP也能使用。

Spring不支持跨越远程调用的事务上下文的propagation。如果需要该功能,推荐使用EJB。

Spring 2.0及以后的版本,不再需要配置 TransactionProxyFactoryBean beans。--之前的仍适用,不推荐。

rollback rules的概念非常重要:它们使得你可以指定什么异常应该导致自动回滚。你在配置中声明式的指定,而非在Java code中。所以,虽然你仍然可以调用TransactionStatus对象的setRollbackOnly()方法来回滚当前事务,多数时候,你可以指定一个规则:MyApplicationException必须导致一个回滚。这样做最大的优势在于业务对象不会依赖于事务设施。例如,不需要导入Spring 事务的APIs或者其他Spring的APIs。

 

虽然EJB容器默认行为是遇到system exception (通常是runtime exception)就自动回滚事务,EJB CMT不会因为application exception(checked exception other than java.rmi.RemoteException)而回滚。Spring声明式事务管理的默认行为同EJB的管理,但可以定制。

 

5.1、理解Spring框架的声明式事务实现

如果只告诉你使用@Transactional来注解你的类,在你的配置中添加@EnableTransactionManagement,然后期望你理解它的全部工作,这样是远远不够的。这一部分解释了在事务相关问题的事件中Spring框架的声明式事务设施的内部工作。

 

这里最重要的概念是Spring框架声明式事务支持是通过AOP代理开启的!!事务的advice是通过元数据(基于XML或注解)驱动的。AOP和事务的元数据结合起来形成了AOP代理--该代理使用TransactionInterceptor配合PlatformTransactionManager实现来驱动围绕方法调用的事务!!

 

概念上,调用事务代理上的方法看起来是这样的:

tx

 

5.2、声明式事务实现的例子

考虑下下面的接口和其实现。这个例子使用Foo和Bar类作为占位符,你可以将精力集中在事务的使用上,而不必关注某一个domain model。DefaultFooService类的每个方法都抛出了UnsupportedOperationException实例,这能让你观察到相应情况下事务的创建与回滚。

// 想使用事务的service接口

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);

}
// 上面接口的实现

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();
    }

}
<!-- 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>

上面假定了FooService的前两个方法(getFoo(String), getFoo(String, String))都必须在只读事务中执行。另外两个方法必须在读写事务中执行。

上面的配置让fooService bean变成事务性的。

常见的需求是让整个service层变成事务性的。最佳做法如下:

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

 

5.3、回滚声明式事务

上面概述了如何指定类的事务设置,特别是service层的类。本部分将描述如何控制声明式事务的回滚。

推荐的方法是从被执行的代码中抛出一个Exception。Spring框架的事务设施会catch所有unhandled Exception,并决定是否让事务回滚。

默认设置下,Spring框架的事务设施只会回滚runtime、unchecked exceptions,就是说抛出的异常是RuntimeException的子类。(默认,Error也会导致回滚)。

不过,你可以配置具体什么Exception类型会导致回滚,可以包括checked exceptions。下面的XML片段演示了如何配置让一个checked、application-specific Exception类型导致回滚。

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

甚至可以指定’no rollback rules’ -- 在什么情况不会回滚!如下:

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

 

当Spring框架的事务设施catch了一个异常,它会咨询配置中的回滚规则以决定是否将该事务标记为回滚,the stronges matching rule wins -- 最符合的匹配规则起作用。下面的配置中,除了InstrumentNotFoundException都会导致回滚!

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

还可以通过编码标记一个回滚!但,虽然简单,但是侵入式的,会将你的代码与Spring框架的事务设施紧密耦合在一起!如下:

public void resolvePosition() {
    try {
        // some business logic...
    } catch (NoProductInStockException ex) {
        // trigger rollback programmatically
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}
强烈建议使用声明式事务来控制回滚,除非不得已,否则不要使用编码式回滚。

 

5.4、为不同的beans配置不同的事务性语义

思考下这种情景:你有大量的service层对象,你还想为每个对象应用一个完全不同的事务性配置。你可以定义不同的<aop:advisor/>--带有不同的pointcut和advice-ref属性值。

作为对比,先假定所有的service层的类都定义在x.y.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>

下面的例子示意了如何为不同的beans配置不同的事务:

<?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>

    <!-- this bean will be transactional (see the 'defaultServiceOperation' pointcut) -->
    <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.5、<tx:advice/>设置

本部分总结了不同的事务性设置 -- 可以通过<tx:advice/>标签指定。

<tx:advice/>默认的设置是:

  • Propagation设置是REQUIRED。
  • Isolation级别是DEFAULT。
  • Transaction是read/write。
  • 事务超时默认是底层事务系统的默认超时,或者没有超时--如果不支持的话。
  • 所有的RuntimeException都会导致回滚,所有的checked Exception不会。

你可以修改这些默认设置。

<tx:advice/>和<tx:attributes/>内部嵌套的<tx:method/>标签的不同属性可以总结为以下内容:

属性 是否必须 默认 描述
name   如:get*, handle*, on*Event等等。
propagation REQUIRED 事务传播行为。
isolation DEFAULT 事务隔离级别。
timeout -1 事务超时时间(秒)。
read-only false 事务是否只读?
rollback-for   导致回滚的异常,多个事务以逗号拼接。
no-rollback-for   不会导致回滚的异常,多个异常时以逗号拼接。

 

5.6、使用@Transactional

@Transactional
public class DefaultFooService implements FooService {

    Foo getFoo(String fooName);

    Foo getFoo(String fooName, String barName);

    void insertFoo(Foo foo);

    void updateFoo(Foo 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"/>

    <!-- 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>

如果PlatformTransactionManager的bean name是transactionManager,那可以省略<tx:annotation/>的transaction-manager属性!否则就需要指定。

@Configuration class中,使用@EnableTransactionManagement具有同样的效果。

当使用代理时,你应该仅在public方法上使用@Transactional注解。虽然private/protected方法上也不会报错,但没有任何效果。

可以在接口、接口的方法、类、类的public方法上使用@Transactional注解。然而,仅仅使用@Transactional注解是不够的,它只是简单的元数据,需要被运行时设施如@Transactional-aware等使用。在前面的例子中,<tx:annotation-driven/>开启了事务性行为。

Spring推荐你只注解在类或其public方法上,而不要注解到接口上。否则,当你你开启了proxy-target-class=”true”或mode=”aspectj”时,会无法识别那些!!!

在proxy mode (默认就是)下,只有外部的方法调用才会被拦截!就是说,类内部方法之间的调用不会开启事务!!!

 

proxy-target-class属性用来控制被@Transactional注解过的类创建什么类型的事务代理。如果true,则基于class的代理会被创建。如果false或者留空,标准JDK的基于接口的代理会被创建!--详见Spring AOP中的代理机制。--要时刻记住,事务本质上是通过AOP实现的!

@EnableTransactionManagement和<tx:annotation-driven/>只会查找相同应用上下文中的@Transactional beasn!!!就是说,如果将注解驱动的配置放到一个DispatcherServlet的WebApplicationContext中,只会检查你的controllers当中的@Transactional beans,而不会检查services。

 

如果类上和类的方法上都有@Transactional注解,那方法上的注解优先于类上的注解!--最近原则。

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

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

    // these settings have precedence for this method
    @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
    public void updateFoo(Foo foo) {
        // do something
    }
}

 

@Transactional设置

默认设置:

  • Propagation设置是PROPAGATION_REQUIRED。
  • Isolation级别是ISOLATION_DEFAULT。
  • Transaction是read/write。
  • Transanction timeout默认是底层事务系统的默认超时时间,或者没有--如果不支持的话。
  • 所有RuntimeException都会导致回滚,所有checked Exception都不会导致回滚。

默认设置可以被改变,见下面@Transactional注解的properties:

属性 类型 描述
value String 可选项,指定使用的事务管理器。
propagation enum:Propagation 可选项,传播行为设置。
Isolation enum:Isolation 可选项,隔离级别。
readOnly boolean read/write vs read-only
timeout int 单位:秒。
rollbackFor Class<? extends Throwable)[] 可选项。
rollbackForClassName   可选项。
noRollbackFor   可选项。
noRollbackForClassName   可选项。

 

@Transactional与 多个事务管理器

多数Spring应用只需要一个事务管理器,但仍有需要多个事务管理器的情况。@Transactional注解的value属性,可以用来指定需要使用的PlatformTransactionManager。既可以是事务管理器bean 的 bean name也可以是其qualifier。例如:

public class TransactionalService {

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

    @Transactional("account")
    public void doSomething() { ... }
}
<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>

如果没有找到指定的事务管理器bean,那会默认使用bean name是transactionManager的bean。

 

自定义注解

@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() { ... }
}

 

5.7、事务传播 propagation

本部分描述了Spring中事务传播的语义。注意,不是对事务传播的介绍,而是Spring中事务传播的一些详细信息。

在Spring管理的事务中,要留意physicallogical事务的区别,以及不同的传播设置。

tx prop required

PROPAGATION_REQUIRED时,会为每个适用的方法创建一个logical事务scope。每个这样的logical transaction scope都能独立的决定rollback-only status,外部事务scope逻辑上独立于内部事务scope。当然,在标准的PROPAGATION_REQUIRED行为里,所有scopes都会被映射到相同的物理事务。所以,内部事务scope中的回滚标记也会影响外部事务的提交!(--什么鬼???看上面的图片)

然而,当内部scope设置的rollback-only marker,外部事务还没决定回滚时,回滚是非预期的。(什么鬼???)。会抛出一个UnexpectedRollbackException。

 

tx prop requires new

PROPAGATION_REQUIRES_NEW,相对于PROPAGATION_REQUIRED来说,为每个涉及到的事务scope都使用了完全独立的事务。所以,底层物理事务是不同的,所以提交或回滚都是彼此独立的,外部事务不会被内部事务的回滚影响。

 

PROPAGATION_NESTED 使用一个单独的物理事务,带有多个可以回滚至的savepoints。这允许内部事务scope在其范围内回滚,而外部事务仍然可以继续物理事务。该设置被映射到JDBC的savepoints,所以只能在JDBC资源事务时工作。参考Spring的DataSourceTransactionManager。

 

5.8、Advising transactional operations

posted on 2016-10-24 14:05  LarryZeal  阅读(662)  评论(0编辑  收藏  举报

导航