• 深入了解Spring之事务


    GitHub:https://github.com/JDawnF/learning_note

    目录

    1、事务

    2、事务的特性

    3、列举 Spring 支持的事务管理类型

    3.1 声明式事务

    a.基于TransactionInterceptor的声明式事务:

    b.基于TransactionProxyFactoryBean的声明式事务

    c.基于命名空间的声明式事务

    d.基于标注(@Transactional)的声明式事务

    3.2 编程式事务

    4、事务管理接口

    4.1 Spring 事务如何和不同的数据持久层框架做集成

    4.2 PlatformTransactionManager接口代码如下:

    4.3 TransactionDefinition接口介绍

    事务属性

    4.4 TransactionStatus接口介绍


    1、事务

    事务就是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作。

    这样可以防止出现脏数据,防止数据库数据出现问题。

    2、事务的特性

    指的是 ACID ,如下图所示:

    1. 原子性 Atomicity :一个事务(transaction)中的所有操作,或者全部完成,或者全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被恢复(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。即,事务不可分割、不可约简。

    2. 一致性 Consistency :在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束触发器级联回滚等。

    3. 隔离性 Isolation :数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

    4. 持久性 Durability :事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

    3、列举 Spring 支持的事务管理类型

    目前 Spring 提供两种类型的事务管理:

    • 声明式事务:基于AOP,通过使用注解或基于 XML 的配置事务,从而事务管理与业务代码分离。

    • 编程式事务:通过编码的方式实现事务管理,需要在代码中显式的调用事务的获得、提交、回滚,提供极大的灵活性,但维护起来非常困难。

    3.1 声明式事务

        Spring的声明式事务管理建立在AOP基础上,其本质是在目标方法执行前进行拦截,在方法开始前创建一个事务,在执行完方法后根据执行情况提交或回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不用侵入业务代码,只需要在配置文件中做相关的事物声明就可将业务规则应用到业务逻辑中。和编程式事务相比,声明式事务唯一的不足是智能作用到方法级别,无法做到像编程式事务那样到代码块级别。

    声明式事务有四种方式:

    a.基于TransactionInterceptor的声明式事务;

    b.基于TransactionProxyFactoryBean的声明式事务;

    c.基于命名空间的声明式事务;

    d.基于标注(@Transactional)的声明式事务。

    a.基于TransactionInterceptor的声明式事务:

      TransactionInterceptor主要有两个属性,一个是transactionManager,用于指定一个事务管理器;另一个是transactionAttributes,通过键值对的方式指定相应方法的事物属性,其中键值可以使用通配符。在config.xml配置文件中加入TransactionInterceptor配置: 

    ...
         <bean id="transactionInterceptor"
               class="org.springframework.transaction.interceptor.TransactionInterceptor">
             <property name="transactionManager" ref="transactionManager"/>
             <property name="transactionAttributes">
                 <props>
                     <prop key="*">PROPAGATION_REQUIRED</prop>
                 </props>
             </property>
         </bean>
         <bean id="userService"
               class="org.springframework.aop.framework.ProxyFactoryBean">
             <property name="target">
                 <bean class="com.xiaofan.test.UserService" />
             </property>
             <property name="interceptorNames">
                 <list>
                     <idref bean="transactionInterceptor"/>
                 </list>
             </property>
         </bean>
     ...
     public class UserService {
         @Resource
         UserDAO userDAO;
         public void addUser3(User user) {
             userDAO.insert(user);
             Integer i = 1;
             if (i.equals(0)) {
             }
         }
     }

    b.基于TransactionProxyFactoryBean的声明式事务

      以上基于TransactionInterceptor的方式每个服务bean都需要配置一个ProxyFactoryBean,这会导致配置文件冗长,为了缓解这个问题,Spring提供了基于TransactionProxyFactoryBean的声明式事务配置方式。在config.xml配置文件中加入TransactionProxyFactoryBean配置:

     
    ...
         <bean id="userService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
             <property name="target">
                 <bean class="com.xiaofan.test.UserService" />
             </property>
             <property name="transactionManager" ref="transactionManager"/>
             <property name="transactionAttributes">
                 <props>
                     <prop key="insert*">PROPAGATION_REQUIRED</prop>
                     <prop key="update*">PROPAGATION_REQUIRED</prop>
                     <prop key="*">PROPAGATION_REQUIRED</prop>
                 </props>
             </property>
         </bean>
     ...

    UserService服务代码如上,不用变更。

    c.基于命名空间的声明式事务

      Spring 2.x引入了命名空间,加上命名空间的切点表达式支持,声明式事务变的更加强大,借助于切点表达式,可以不需要为每个业务类创建一个代理。为了使用动态代理,首先需要添加pom依赖:

    ...
             <dependency>
                 <groupId>org.aspectj</groupId>
                 <artifactId>aspectjweaver</artifactId>
                 <version>1.7.4</version>
             </dependency>
     ...

    config.xml文件添加如下配置:

    <bean id="userService" class="com.xiaofan.test.UserService">
         </bean>
         <tx:advice id="userAdvice" transaction-manager="transactionManager">
             <tx:attributes>
                 <tx:method name="*" propagation="REQUIRED"/>
             </tx:attributes>
         </tx:advice>
         <aop:config>
             <!--匹配模式,匹配对应的方法,参照上面的AOP-->
             <aop:pointcut id="userPointcut" expression="execution (* com.xiaofan.test.*.*(..))"/>
             <aop:advisor advice-ref="userAdvice" pointcut-ref="userPointcut"/>
         </aop:config>

    UserService服务代码如上,不用变更。

    d.基于标注(@Transactional)的声明式事务

      除了基于命名空间的事务配置方式外,Spring2.x还引入了基于注解的方式,主要涉及@Transactional注解,它可以作用于接口、接口方法、类和类的方法上,当做用于类上时,该类的所有public方法都有该类型的事务属性,可被方法级事务覆盖。在config.xml配置文件中加入注解识别配置:

    <tx:annotation-driven transaction-manager="transactionManager"/>

     @Transactional注解应该被应用到public方法上,这是由AOP的本质决定的,如果应用在protected、private的方法上,事务将被忽略。UserService服务代码如下:

    public class UserService {
         @Resource
         UserDAO userDAO;
         @Transactional(propagation = Propagation.REQUIRED)
         public void addUser4(User user) {
             userDAO.insert(user);
             Integer i = 1;
             if (i.equals(0)) {
     ​
             }
         }
     }

    @Transactional注解的完整属性信息如下表:

    基于命名空间和基于注解的事务声明各有优缺点:基于命名空间的方式一个配置可以匹配多个方法,但配置较注解方式复杂;基于注解的方式需要在每个需要使用事务的方法或类上标注,但基于标注的方法学习成本更低。

    3.2 编程式事务

    一般的使用:

    Spring配置文件config.xml如下:

    <?xml version="1.0" encoding="UTF-8"?>
     <beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns="http://www.springframework.org/schema/beans"
            xmlns:tx="http://www.springframework.org/schema/tx"
            xmlns:context="http://www.springframework.org/schema/context"
            xsi:schemaLocation="
                 http://www.springframework.org/schema/beans
                     http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
                 http://www.springframework.org/schema/tx
                     http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
                 http://www.springframework.org/schema/context
                     http://www.springframework.org/schema/context/spring-context-3.0.xsd">
         <!-- 引入属性文件 -->
         <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
             <property name="locations">
                 <list>
                     <value>classpath:config.properties</value>
                 </list>
             </property>
         </bean>
         <!-- 配置数据源 -->
         <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
             <property name="driverClassName">
                 <value>${driver}</value>
             </property>
             <property name="url">
                 <value>${url}</value>
             </property>
             <property name="username">
                 <value>${username}</value>
             </property>
             <property name="password">
                 <value>${password}</value>
             </property>
         </bean>
     ​
         <!-- 自动扫描了所有的mapper配置文件对应的mapper接口文件 -->
         <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
             <property name="basePackage" value="com.xiaofan.test" />
         </bean>
         <!-- 配置Mybatis的文件 -->
         <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
             <property name="dataSource" ref="dataSource" />
             <property name="mapperLocations" value="classpath:user_mapper.xml"/>
             <property name="configLocation" value="classpath:mybatis_config.xml" />
         </bean>
         <!-- 配置JDBC事务管理器 -->
         <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
             <property name="dataSource" ref="dataSource" />
         </bean>
         <bean id="userService" class="com.xiaofan.test.UserService">
         </bean>
     </beans>

    根据PlatformTransactionManager、TransactionDefinition和TransactionStatus三个接口,可以通过编程的方式来进行事务管理, TransactionDefinition实例用于定义一个事务,PlatformTransactionManager实例用语执行事务管理操作,TransactionStatus实例用于跟踪事务的状态。UserService服务中配置如下:

    public class UserService {
         @Resource
         UserDAO userDAO;
         @Resource
         DataSource dataSource;
         @Resource
         PlatformTransactionManager transactionManager;
         public void addUser(User user) throws Exception {
             TransactionDefinition def = new DefaultTransactionDefinition();
             TransactionStatus status = transactionManager.getTransaction(def);
             try {
                 // [1] 插入纪录
                 userDAO.insert(user);
                 // [2] 范例抛出异常
                 Integer i = null;
                 if (i.equals(0)) {
     ​
                 }
                 transactionManager.commit(status);
             } catch (Exception e) {
                 transactionManager.rollback(status);
                 throw e;
             }
             return;
         }
     }

    Spring测试代码如下:

    @ContextConfiguration(locations = {"classpath:config.xml"})
     @RunWith(SpringJUnit4ClassRunner.class)
     public class Test extends AbstractJUnit4SpringContextTests{
     ​
         @Resource
         UserService userService;
     ​
         @org.junit.Test
         public void testAdd() {
             try {
                 userService.addUser(new User(null, "LiLei", 25));
             } catch (Exception e) {
             }
         }
     }

    如果[2]处抛出异常,则事务执行回滚,如果[2]没有抛出异常,则提交执行纪录插入操作。

    另一种编程式事务管理

    以上这种事务管理方式容易理解,但事务管理代码散落在业务代码中,破坏了原有代码的条理性,且每个事务方法中都包含了启动事务、提交/回滚事务的功能,基于此,Spring提供了模板方法模式(TransactionTemplate)。

    在config.xml配置文件中加入TransactionTemplate bean配置:

    <bean id="transactionTemplate"  class="org.springframework.transaction.support.TransactionTemplate">
             <property name="transactionManager" ref="transactionManager"/>
    </bean>

    TransactionTemplate 的execute()方法有一个TransactionCallback类型的参数,该接口中定义了一个doInTransaction()方法,可通过匿名内部累的方式实现TransactionCallBack接口,将业务代码写在doInTransaction()方法中,业务代码中不需要显示调用任何事物管理API,除了异常回滚外,也可以在业务代码的任意位置通过transactionStatus.setRollbackOnly();执行回滚操作。UserService服务代码变更为:

    public class UserService {
         @Resource
         UserDAO userDAO;
         @Resource
         TransactionTemplate transactionTemplate;
         public void addUser(final User user) {
             transactionTemplate.execute(new TransactionCallback() {
                 public Object doInTransaction(TransactionStatus transactionStatus) {
                     userDAO.insert(user);
                     // transactionStatus.setRollbackOnly();
                     Integer i = null;
                     if (i.equals(0)) {
     ​
                     }
                     return null;
                 }
             });
         }
     }
    

    实际场景下,我们一般使用 Spring Boot + 注解的声明式事务。参照 《Spring Boot 事务注解详解》

    另外,也推荐看看 《Spring 事务管理 - 编程式事务、声明式事务》 一文。

    4、事务管理接口

    • PlatformTransactionManager: (平台)事务管理器

    • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)

    • TransactionStatus: 事务运行状态

    所谓事务管理,其实就是“按照给定的事务规则来执行提交或者回滚操作”。

    4.1 Spring 事务如何和不同的数据持久层框架做集成

    Spring并不直接管理事务,而是提供了多种事务管理器 ,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是: org.springframework.transaction.PlatformTransactionManager ,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

    4.2 PlatformTransactionManager接口代码如下:

    PlatformTransactionManager接口中定义了三个方法:

    // PlatformTransactionManager.java
     ​
     public interface PlatformTransactionManager {
     ​
         // 根据事务定义 TransactionDefinition ,获得 TransactionStatus 。根据指定的传播行为,返回当前活动的事务或创建一个新事务。 
         TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
         // 根据情况,提交事务,使用事务目前的状态提交事务
         void commit(TransactionStatus status) throws TransactionException;
         // 根据情况,回滚事务,对执行的事务进行回滚
         void rollback(TransactionStatus status) throws TransactionException; 
     }
    • PlatformTransactionManager 是负责事务管理的接口,一共有三个接口方法,分别负责事务的获得、提交、回滚。

    • getTransaction(TransactionDefinition definition)方法,根据事务定义 TransactionDefinition ,获得 TransactionStatus 。

      • 为什么不是创建事务呢?因为如果当前如果已经有事务,则不会进行创建,一般来说会跟当前线程进行绑定。如果不存在事务,则进行创建。

      • 为什么返回的是 TransactionStatus 对象?在 TransactionStatus 中,不仅仅包含事务属性,还包含事务的其它信息,例如是否只读、是否为新创建的事务等等。

    • commit(TransactionStatus status)方法,根据 TransactionStatus 情况,提交事务。

      • 为什么根据 TransactionStatus 情况,进行提交?例如说,带@Transactional注解的的 A 方法,会调用

        @Transactional注解的的 B 方法。

        • 在 B 方法结束调用后,会执行 PlatformTransactionManager#commit(TransactionStatus status) 方法,此处事务是不能也不会提交的。

        • 而是在 A 方法结束调用后,执行 PlatformTransactionManager#commit(TransactionStatus status) 方法,提交事务。

    • rollback(TransactionStatus status)方法,根据 TransactionStatus 情况,回滚事务。

      • 为什么根据 TransactionStatus 情况,进行回滚?原因同 commit(TransactionStatus status) 方法。

    Spring中PlatformTransactionManager根据不同持久层框架所对应的接口实现类,几个比较常见的如下图所示

    org.springframework.transaction.support.AbstractPlatformTransactionManager ,基于 模板方法模式 ,实现事务整体逻辑的骨架,而抽象 doCommit(DefaultTransactionStatus status)doRollback(DefaultTransactionStatus status) 等等方法,交由子类类来实现。

    ④ 最后,不同的数据持久层框架,会有其对应的 PlatformTransactionManager 实现类,如下图所示:

    • 所有的实现类,都基于 AbstractPlatformTransactionManager 这个骨架类。

    • HibernateTransactionManager ,和 Hibernate5 的事务管理做集成。

    • DataSourceTransactionManager ,和 JDBC 的事务管理做集成。所以,它也适用于 MyBatis、Spring JDBC 等等。

    • JpaTransactionManager ,和 JPA 的事务管理做集成。

    如下,是一个比较常见的 使用JDBC或者iBatis(就是Mybatis)进行数据持久化操作的XML 方式来配置的事务管理器,使用的是 DataSourceTransactionManager 。代码如下:

    <!-- 事务管理器 -->
     <bean id="transactionManager"
     class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
         <!-- 数据源 -->
         <property name="dataSource" ref="dataSource" />
     </bean>

    4.3 TransactionDefinition接口介绍

    事务管理器接口 PlatformTransactionManager 通过 getTransaction(TransactionDefinition definition) 方法来得到一个事务,这个方法里面的参数是 TransactionDefinition类 ,这个类就定义了一些基本的事务属性。

    事务属性

    事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面。

    TransactionDefinition接口中的方法如下:

    TransactionDefinition接口中定义了5个方法以及一些表示事务属性的常量比如隔离级别、传播行为等等的常量。

    下面只是列出了TransactionDefinition接口中的方法及常量隔离如下:

    public interface TransactionDefinition {
         // 返回事务的传播行为
         int getPropagationBehavior(); 
         // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
         int getIsolationLevel(); 
         String getName();    //返回事务的名字
         int getTimeout();      // 返回事务必须在多少秒内完成
         boolean isReadOnly();    // 返回是否优化为只读事务。
     /**
      * 【Spring 独有】使用后端数据库默认的隔离级别
      * MySQL 默认采用的 REPEATABLE_READ隔离级别
      * Oracle 默认采用的 READ_COMMITTED隔离级别
      */
         int ISOLATION_DEFAULT = -1;
     ​
     /**
      * 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读
      */
         int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
     /**
      * 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生
      */
         int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
     /**
      * 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
      */
         int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
     /**
      * 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
      * 但是这将严重影响程序的性能。通常情况下也不会用到该级别。
      */
         int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
         // ========== 支持当前事务的情况 ========== 
     /**
      * 如果当前存在事务,则使用该事务。
      * 如果当前没有事务,则创建一个新的事务。
      */
         int PROPAGATION_REQUIRED = 0;
     /**
      * 如果当前存在事务,则使用该事务。
      * 如果当前没有事务,则以非事务的方式继续运行。
      */
         int PROPAGATION_SUPPORTS = 1;
     /**
      * 如果当前存在事务,则使用该事务。
      * 如果当前没有事务,则抛出异常。
      */
         int PROPAGATION_MANDATORY = 2;
     ​
     // ========== 不支持当前事务的情况 ========== 
     /**
      * 创建一个新的事务。
      * 如果当前存在事务,则把当前事务挂起。
      */
         int PROPAGATION_REQUIRES_NEW = 3;
     /**
      * 以非事务方式运行。
      * 如果当前存在事务,则把当前事务挂起。
      */
         int PROPAGATION_NOT_SUPPORTED = 4;
     /**
      * 以非事务方式运行。
      * 如果当前存在事务,则抛出异常。
      */
         int PROPAGATION_NEVER = 5;
     ​
     // ========== 其他情况 ========== 
     /**
      * 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行。
      * 如果当前没有事务,则等价于 {@link TransactionDefinition#PROPAGATION_REQUIRED}
      */
         int PROPAGATION_NESTED = 6;
     } 
    

    (1)事务隔离级别(定义了一个事务可能受其他并发事务影响的程度):

    并发事务带来的问题

    在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务(多个用户对统一数据进行操作)。并发虽然是必须的,但可能会导致一下的问题。

    • 脏读(Dirty read): 当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外一个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。

    • 丢失修改(Lost to modify): 指在一个事务读取一个数据时,另外一个事务也访问了该数据,那么在第一个事务中修改了这个数据后,第二个事务也修改了这个数据。这样第一个事务内的修改结果就被丢失,因此称为丢失修改。

      • 例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。

    • 不可重复读(Unrepeatableread): 指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。

    • 幻读(Phantom read): 幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。

    不可重复度和幻读区别:

    不可重复读的重点是修改,幻读的重点在于新增或者删除。

    例1(同样的条件, 你读取过的数据, 再次读取出来发现值不一样了 ):事务1中的A先生读取自己的工资为 1000的操作还没完成,事务2中的B先生就修改了A的工资为2000,导致A再读自己的工资时工资变为 2000;这就是不可重复读。

    例2(同样的条件, 第1次和第2次读出来的记录数不一样 ):假某工资单表中工资大于3000的有4人,事务1读取了所有工资大于3000的人,共查到4条记录,这时事务2 又插入了一条工资大于3000的记录,事务1再次读取时查到的记录就变为了5条,这样就导致了幻读。

    隔离级别

    TransactionDefinition 接口中定义了五个表示隔离级别的常量:

    • TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的 REPEATABLE_READ隔离级别,Oracle 默认采用的 READ_COMMITTED隔离级别.

    • TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

    • TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

    • TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。

    • TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

    (2)事务传播行为(为了解决业务层方法之间互相调用的事务问题):

    当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

    支持当前事务的情况:

    • TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。

    • TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。

    • TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

    不支持当前事务的情况:

    • TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。

    • TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。

    • TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。 其他情况:

    • TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

    这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。

    类似于 JDBC 中的保存点(SavePoint)的概念,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。

    (3) 事务超时属性(一个事务允许执行的最长时间)

    所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

    (4) 事务只读属性(对事物资源是否执行只读操作)

    事务的只读属性是指,对事务性资源进行只读操作或者是读写操作。所谓事务性资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。如果确定只对事务性资源进行只读操作,那么我们可以将事务标志为只读的,以提高事务处理的性能。在 TransactionDefinition 中以 boolean 类型来表示该事务是否只读。

    (5) 回滚规则(定义事务回滚规则)

    这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。 ​ 但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

    4.4 TransactionStatus接口介绍

    TransactionStatus接口用来记录事务的状态 该接口定义了一组方法,用来获取或判断事务的相应状态信息。

    PlatformTransactionManager.getTransaction(…) 方法返回一个 TransactionStatus 对象。返回的TransactionStatus 对象可能代表一个新的或已经存在的事务(如果在当前调用堆栈有一个符合条件的事务)。

    TransactionStatus接口接口内容如下:

    public interface TransactionStatus{
         boolean isNewTransaction(); // 是否是新的事物
         boolean hasSavepoint(); // 是否有恢复点,在 {@link TransactionDefinition#PROPAGATION_NESTED} 传播级别使用。
         void setRollbackOnly();  // 设置为只回滚
         boolean isRollbackOnly(); // 是否为只回滚
         boolean isCompleted; // 是否已完成
     } 
    • 为什么没有事务对象呢?在 TransactionStatus 的实现类 DefaultTransactionStatus 中,有个 Object transaction 属性,表示事务对象。

    • isNewTransaction() 方法,表示是否是新创建的事务。作用主要参照上面的 commit(TransactionStatus status) 方法的解释。通过该方法,我们可以判断,当前事务是否当前方法所创建的,只有创建事务的方法,才能且应该真正的提交事务

    参照: 可能是最漂亮的Spring事务管理详解

    芋道源码

  • 相关阅读:
    php xdebug的配置、调试、跟踪、调优、分析
    alpine使用的避坑指南
    nginx fastcgi模块ngx_http_fastcgi_module详细解析、使用手册、完整翻译
    深入理解 Kubernetes 资源限制:CPU
    使用xdebug对php做性能分析调优
    alpine安装sshd/ssh server
    冒泡排序的终极改进优化
    基于Maven的Springboot+Mybatis+Druid+Swagger2+mybatis-generator框架环境搭建
    NPM使用
    NodeJS学习历程
  • 原文地址:https://www.cnblogs.com/baichendongyang/p/13235443.html
Copyright © 2020-2023  润新知