• 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

  • 相关阅读:
    算法---递归及尾递归
    ScheduledThreadPoolExecutor之remove方法
    数据结构---队列及简单实现有界队列
    数据结构---栈及四则运算实现
    数据结构---链表及约瑟夫环问题带来的思考
    数据结构---数组
    时间复杂度和空间复杂度
    Redis缓存设计与性能优化
    Springboot+ELK实现日志系统简单搭建
    Docker学习笔记(三):Dockerfile及多步骤构建镜像
  • 原文地址:https://www.cnblogs.com/larryzeal/p/5992735.html
Copyright © 2020-2023  润新知