spring事务传播机制使用及原理

489 阅读7分钟

事务

事务是逻辑上的一组操作,要么都执行,要么都不执行。 事务的四大特性 :

原子性,构成事务的所有操作,要么都执行完成,要么全部不执行,不可能出现部分成功部分失 败的情况。

一致性,在事务执行前后,数据库的一致性约束没有被破坏。

隔离性,数据库中的事务一般都是并发的,隔离性是指并发的两个事务的执行互不干扰,一个事 务不能看到其他事务运行过程的中间状态。

持久性,事务完成之后,该事务对数据的更改会被持久化到数据库,且不会被回滚。

spring编程式事务管理

手动管理事务,通过spring的 TransactionTemplate、transactionManager或者mybatis的sqlsession手动管理事务,变成控制事务提交和回滚,实际应用中很少使用。

@Autowired
private PlatformTransactionManager transactionManager;
​
public void testTransaction() {
​
  TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
          try {
               // ....  业务代码
              transactionManager.commit(status);
          } catch (Exception e) {
              transactionManager.rollback(status);
          }
}
​

spring声明式事务管理

声明式事务管理可以使用aop txadvice 配置类,也可以使用xml aop配置,还可以基于@Transactional 的注解,该方式使用最多,因为@Transactional可以用在方法上,颗粒度更细。三者的原理上都是一致的,利用spring aop原理,注入动态代理类,调用事务拦截器TransactionInterceptor,数据的提交会设置成手动提交,来进行事务传播管理。

@Configuration
public class TxAnoConfig {
​
  
    @Autowired
    private DataSource dataSource;
​
    @Bean("txManager")
    public DataSourceTransactionManager txManager() {
        return new DataSourceTransactionManager(dataSource);
    }
​
    /*事务拦截器*/
    @Bean("txAdvice")
    public TransactionInterceptor txAdvice(DataSourceTransactionManager txManager){
        NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
          /*只读事务,不做更新操作*/
         RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
         readOnlyTx.setReadOnly(true);
         readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED );
         RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED,
             Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
         requiredTx.setTimeout(5);
         Map<String, TransactionAttribute> txMap = new HashMap<>();
         txMap.put("add*", requiredTx);
         txMap.put("save*", requiredTx);
         txMap.put("insert*", requiredTx);
         txMap.put("update*", requiredTx);
         txMap.put("delete*", requiredTx);
         txMap.put("get*", readOnlyTx);
         txMap.put("query*", readOnlyTx);
         txMap.put("select*", readOnlyTx);
         source.setNameMap( txMap );
        return new TransactionInterceptor(txManager ,source) ;
    }
 
    /**切面拦截规则 参数会自动从容器中注入*/
    @Bean
    public DefaultPointcutAdvisor defaultPointcutAdvisor(TransactionInterceptor txAdvice){
        DefaultPointcutAdvisor pointcutAdvisor = new DefaultPointcutAdvisor();
        pointcutAdvisor.setAdvice(txAdvice);
        AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
        pointcut.setExpression("execution (* com.*.*.service.order.*.*(..))");
        pointcutAdvisor.setPointcut(pointcut);
        return pointcutAdvisor;
    }
 
 
}
<?xml version="1.0" encoding="UTF-8"?><beans  xmlns="http://www.springframework.org/schema/beans"
        ......
​
        http://www.springframework.org/schema/aop/spring-aop-3.0.xsd">
​
    <bean id="txManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
​
    <!-- 使用tx标签配置的拦截器 -->
​
    <!-- 定义事务 -->
​
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="select*" propagation="SUPPORTS" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED" rollback-for="Exception"/>
        </tx:attributes>
    </tx:advice>
​
    <!-- 定义切面 -->
​
    <aop:config>
​
        <aop:pointcut id="interceptorPointcut" expression="execution (* com.*.*.service.order.*.*(..))" />
​
        <aop:advisor advice-ref="txAdvice" pointcut-ref="interceptorPointcut" order="0"/>
​
    </aop:config></beans>

@Transactional 和AOP 配置类或者XML配置都存在时,两者的aop拦截器都会注册,根据注册顺序来定义方法的事务属性,后面的会覆盖前面的,一般来说注解类的会覆盖配置类的事务定义。

事务管理类

Spring 框架中,事务管理相关最重要的 3 个接口如下:

PlatformTransactionManager: (平台)事务管理器,Spring 事务策略的核心,数据源连接,事务的管理者。 TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)。 TransactionStatus:事务运行状态 TransactionSynchronizationManager :事务后置处理,使用ThreadLocal来为不同的事务线程提供独立的资源副本,并且同时维护这些事务的配置属性和运行状态。

PlatformTransactionManager提供了数据源的连接池的连接,可以重中获取连接不同平台的数据库连接实例,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,PlatformTransactionManager 会根据 TransactionDefinition 的定义比如事务超时时间、隔s离级别、传播行为等来进行事务管理 ,而 TransactionStatus 接口则提供了一些方法来获取事务相应的状态比如是否新事务、是否可以回滚等等。

image.png

事务传播行为

所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:

TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。一个事务,要么成功,要么失败。 TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。两个不同事务,彼此之间没有关系。一个事务失败了不影响另一个事务。 TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。 TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。 TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。 TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

image.png

事务隔离级别

隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。 TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。 TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,没有行锁,这也是大多数情况下的推荐值。 TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。但是可能出现幻像读。原因是开了行锁,禁止了事务开启期间修改内容,不影响继续新增行,所以还是会幻读。 TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

事务超时

所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。 默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

事务只读属性

只读事务用于客户代码只读但不修改数据的情形,修改数据会会报错,SET TRANSACTION READ ONLY类似于SERIALIZABLE事务隔离级别,在发布SET TRANSACTION READONLY起的所有SELECT语句,其结果均为同一个时间点一致,直至显式地发布了COMMIT或ROLLBACK命令或隐式提交(执行DDL)。只读事务用于特定情景下的优化,适合用在有多条数据库查询操作的方法中,保证读取事务数据一致性。

事务回滚规则

@Transactional(rollbackFor= MyException.class),定义了哪些异常会导致事务回滚而。默认情况下 ,事务只有遇到运行期异常(RuntimeException 的子类)时才会回滚,Error 也会导致事务回滚,但是,在遇到检查型(Checked)异常时不会回滚。

spring事务使用常见问题

  1. 如果同一个类中的其他没有@Transactional注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务会失效。

    Spring AOP代理的原因造成的,因为只有当 @Transactional 注解的方法在类以外被调用的时候,Spring 事务管理才生效。换句话说当执行一个类中的普通方法时,Spring没有生成代理对象,即使这个方法内部调用了同类其他的事务方法,也就无法参与事务管理了。

  2. A类的事务方法中调用了B类的事务方法,B方法发生了异常,但是在A类的try-catch模块中捕获了这个异常并正常执行,"Transaction rolled back because it has been marked as rollback-only"

    B方法发生了异常,给当前事务打上了rollbackOnly标记,但是被A捕获到了并正常执行,由于只有一个事务,等到A方法要提交这一个事务的时候,没有发现异常但是发现事务被打上了rollbackOnly,只能回滚并抛出一个unexpectedRollback异常。

\