@Transactional注解失效了?!

269 阅读4分钟

@Transactional注解失效了?!

最近在做一个表与表关联性很强的项目。越发觉得事务的重要性。

什么是事务?一言蔽之,事务就是逻辑上的一组操作,要么都执行,要么都不执行。

在后端写关联性强的复杂的时候,我们往往同样会遇到如果有一条sql失效,之前执行的所有sql都需要回滚的情况。

SpringBoot实现事务

在springboot中,我们往往有两种策略去实现事务,分别是编程式事务和声明式事务。

声明式事务

我们往往用@Trasactional注解去实现。先看一下该注解的源码

//该注解作用于类和方法上
@Target({ElementType.TYPE, ElementType.METHOD})
//设定生命周期为全周期,支持利用反射机制的代码读取和阅读
@Retention(RetentionPolicy.RUNTIME)
//表示,如果被该注解修饰的父类被子类继承,那么子类也具备事务特性
@Inherited
//表示该注解应该被javaDoc工具记录
@Documented
public @interface Transactional {}

该注解有几个常用的参数

1.isolation

  • Isolation.DEFAULT:使用各个数据库默认的隔离级别【默认】
  • Isolation.READ_UNCOMMITTED:读取未提交数据(会出现脏读, 不可重复读)(基本不使用)
  • Isolation.READ_COMMITTED:读取已提交数据(会出现不可重复读和幻读)
  • Isolation.REPEATABLE_READ:可重复读(会出现幻读)
  • Isolation.SERIALIZABLE:串行化

MYSQL:默认为REPEATABLE_READ

2.propagation

  • Propagation.REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是 最常见的选择,也是Spring【默认】的传播机制
  • Propagation.SUPPORTS:持当前事务,如果当前有事务,就以事务方式执行;如果当前没有事务,就以非事务方式执行
  • Propagation.MANDATORY:使用当前的事务,且必须在一个已有的事务中执行,如果当前不存在事务,否则抛出异常
  • Propagation.REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
  • Propagation.NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,就把当前事务挂起
  • Propagation.NEVER:以非事务方式执行,且必须在一个没有的事务中执行,如果当前存在事务,则抛出异常【与Propagation.MANDATORY相反】
  • Propagation.NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则执行与【Propagation.REQUIRED 】类似的操作

3.readOnly

默认是false,设置为true的含义是,告诉spring,该方法是只读操作,如果出现写就抛异常

4.noRollbackFor 和 noRollbackForClassName(遇到时不回滚)

用来指明不回滚的条件是哪些异常类或者异常类名。

5.rollbackFor 和 rollbackForClassName(遇到时回滚)

Spring默认情况下会对运行期异常(RunTimeException)进行事务回滚,如果遇到checked异常就不回滚。

6.timout(超时时间)

用于处理事务处理的事件长度,阻止可能长时间阻塞系统或者占用系统资源,单位是s,如果超时事务回滚,抛出Transaction TimedOutException

7.value(指定使用的事务管理器)

你可能在spring中有多个事务管理器,用来控制多数据源,此时需要指定。

编程式事务

编程式事务的优点是可以在方法里细粒度控制事务,缺点是代码侵入性强。

在spring中,我们如果需要自己配置事务管理器可以这么写。

@Configuration
public class TransactionConfig {
​
    @Bean
    public PlatformTransactionManager platformTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

注意:我这里的PlatformTransactionManager是面向mybatis的事务管理器。

   @Resource
    private PlatformTransactionManager transactionManager;
TransactionStatus transactionStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
        //细粒度事务处理
        try {
            int update = faSaasGamerecordMapper.update(faSaasGamerecord,
                    new UpdateWrapper<FaSaasGamerecord>()
                            .eq("id", faSaasGamerecord.getId())
                            .set("newPlayer", newPlayerCount)
                            .set("timeavg", averageTimeCount)
                            .set("playCount", playRecordCount)
                            .set("shareCount", playerShareCount)
                            .set(UPDATE_TIME,TimeUtil.nowToTimeStamp())
            );
            if(update<1){
                throw new BusinessException(ErrorCode.SYSTEM_ERROR,"更新失败");
            }
            transactionManager.commit(transactionStatus);
        }catch (Exception e){
            log.error("FaSaasGamerecord更新失败");
            transactionManager.rollback(transactionStatus);
        }

通过向transactionManager提交事务来完成事务的操作。如果失败了,就将当前的事务回滚。

一般把多条sql和commit写在try 语句中,catch则操作回滚行为。

@Transactional失效的6种场景

1.修饰在非public方法上

protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
}

这是spring源码种的AbstractFallbackTransactionAttributeSourcecomputeTransactionAttribute 方法。

如果Modifier.isPublic()是false 那么就return null.

2.配置错误的propagation

注意加粗的部分。

主要是以下的三种:

  • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
  • TransactionDefinition.PROPAGATION_NOT_SUPPORTED以非事务方式运行,如果当前存在事务,则把当前事务挂起。
  • TransactionDefinition.PROPAGATION_NEVER以非事务方式运行,如果当前存在事务,则抛出异常。

3.rollbackfor指定错误

Spring默认是抛出unchecked异常,他是继承于RuntimeException,或者error才会回滚事务;如果抛出除此之外的其它异常,那就不会给你回滚了。

4.同一个类中的方法调用。

这是最常见的错误。比如有一个类Test,它的一个方法A,A再调用本类的方法B(不论方法B是用public还是private修饰),但方法A没有声明注解事务,而B方法有。则外部调用方法A之后,方法B的事务是不会起作用的。这也是经常犯错误的一个地方

5.异常被catch吸收

这个都懂

6.数据库引擎不支持事务