@Transactional失效的几种情况

可以作用的地方

  • 类:表示该类的所有public方法都会配置相同的事务属性信息

  • 方法:当类配置了@Transactional,方法也配置了@Transactional,方法的事务会覆盖类的事务属性信息

  • 接口:不推荐使用,一旦注解在interface上并且配置了Spring AOP使用CGLib动态代理,将会导致@Transactional失效

属性

propagation

代表事务的传播级别,默认Propagation.REQUIRED

  • Propagation.REQUIRED:
    如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务
  • Propagation.SUPPORTS
    如果当前存在事务,则加入事务,没有则一飞事务方式运行
  • Propagation.MANDATORY
    当前存在事务,则加入事务,不存在事务则抛出异常
  • Propagation.REQUIRED_NEW
    重新创建一个新的事务,如果当前存在事务,则暂停当前事务( 当类A中的 a 方法用默认Propagation.REQUIRED模式,类B中的 b方法加上采用 Propagation.REQUIRES_NEW模式,然后在 a 方法中调用 b方法操作数据库,然而 a方法抛出异常后,b方法并没有进行回滚,因为Propagation.REQUIRES_NEW会暂停 a方法的事务 )
  • Propagation.NOT_SUPPORTED
    以非事务方式进行,当前存在事务,暂停当前事务
  • Propagation.NEVER
    以非事务方式运行,如果当前存在事务抛出异常
  • Propagation.NESTED
    嵌套事务

isolation

事务隔离级别,默认Isolation.DEFAULT(底层数据库默认隔离级别)

  • Isolation.READ_UNCOMMITTED
  • Isolation.READ_COMMITTED
  • Isolation.REPEATABLE_READ
  • Isolation.SERIALIZABLE

timeout

事务超时时间,默认-1.如果超过该时间事务依然没有完成,回滚

readonly

指定事务为只读事务,默认false,为了忽略那些不需要事务的方法,比如读取数据,可以设置read-only为true

rollbackFor

触发事务回滚的异常,可以指定多个

norollbackFor

抛出指定的异常,不回滚事务,可以指定多个

失效的场景

  1. @Transaction应用在非public修饰的方法上
    image
    AOP代理的时候,在上图TransactionInterceptor(事务拦截器)在目标方法执行前后进行拦截,DynamicAdvisedInterceptor(CGLIBAOPProxy的内部类)的intercept方法或者JdkDynamicAopProxy的invoke方法会间接调用AbstractFallbackTransactionAttributeSource的computeTransactionAttribute方法获取Transaction注解的事务配置信息
protected TransactionAttribute computeTransactionAttribute(Method method,
    Class<?> targetClass) {
        // Don't allow no-public methods as required.
        if (allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
        return null;
}

此方法会检查目标方法的修饰符是否为public ,不是public的不好获取Transaction注解,虽然事务无效,但不会有任何报错

  1. 注解属性propagation设置错误

以下三种情况,事务将不会进行回滚

TransactionDefinition.PROPAGATION_SUPPORTS:

TransactionDefinition.PROPAGATION_NOT_SUPPORTED

TransactionDefinition.PROPAGATION_NEVER

  1. 注解属性rollbackFor设置错误
    rollbackFor可以指定能够触发事务回滚的异常类型,Spring默认抛出未检出unchecked的异常(继承RunTimeException的异常)或者Error才会回滚事务,其他异常不会触发事务,如果事务中抛出其他异常类型,需要指定rollbackFor属性
// 希望自定义的异常可以进行回滚
@ransactional(propagation= Propagation.REQUIRED,rollbackFor= MyException.class

如果抛出的异常事务rollbackFor指定异常的子类,事务同样会进行回滚

 private int getDepth(Class<?> exceptionClass, int depth) {
         if (exceptionClass.getName().contains(this.exceptionName)) {
             // Found it!
             return depth;
 }
         // If we've gone as far as we can go and haven't found it...
         if (exceptionClass == Throwable.class) {
             return -1;
 }
 return getDepth(exceptionClass.getSuperclass(), depth + 1);
 }
  1. 同一个类中的方法调动
    开发中避免不了同一个类中的方法调用,类test中的A方法调用B方法,A没有声明注解事务,B方法有,外部调用方法A之后,B的事务是不会起效果的,原因是由于使用Spring AOP代理造成的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理

  2. 异常被catch,导致@Transaction失效

      @Transactional
      private Integer A() throws Exception {
          int insert = 0;
          try {
              CityInfoDict cityInfoDict = new CityInfoDict();
              cityInfoDict.setCityName("2");
              cityInfoDict.setParentCityId(2);
              /**
               * A 插入字段为 2的数据
               */
              insert = cityInfoDictMapper.insert(cityInfoDict);
              /**
               * B 插入字段为 3的数据
               */
              b.insertB();
          } catch (Exception e) {
              e.printStackTrace();
          }
      }

当B方法内部抛出异常,此时A方法try catchB方法的异常,那么这是事务能正常回滚吗?

答案是不能
会抛出异常

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

当ServiceB中抛出异常,ServiceB标识当前事务需要rollback。但是ServiceA中由于你手动捕获了一个异常并处理,ServiceA会认为当前事务应该正常commit,这个时候就会出现前后不一致,这样就会出现上面的异常

Spring的事务在调用业务方法之前开始的,业务方法执行完毕之后才会执行commit 或者rollback,事务是否执行取决于是否爬出runtime异常,如果抛出runtime 异常,并在你的业务代码中并有catch到,事务就会回滚

在业务方法中一般不需要catch异常,非要catch就一定要throw new RunTimeExecption() 或者抛出注解中指定会回滚的异常,否则将会导致事务失效,数据commit造成数据不一致

  1. 数据库存储引擎不支持
    比如MyISAM