@Transactional的失效场景

@Transactional我们在日常开发中经常用到,可能会经常遇到@Transactional失效的情况。下面将从what、where、when三个方面讲解@Transactional。

什么是事务?(what)

  事务(transaction)是指我们做的一系列完整的事情,在事务中,任何一步出现了问题,这件事情就未算完成。这里的事务是指对数据库的增删改操作,事务的操作具有以下特性:

A(Atomicity)

原子性:事务是个完整的个体,事务内的操作要么全部成功,要么全部失败。

C(Isolation)

一致性:事务中的操作结果要保证数据达到预期的结果。

I(Consistency)

隔离性:一个事务的操作对另一个事务时不可见的。

D(Durability)

持久性:事务的操作结果要存储在数据库中,具有持久性。

编程式事务

  是指在代码中实现事务的开启、提交或回滚等操作,代码的侵入性较强。编程式事务可以在代码中精确的定位事务的边界。对于编程式事务管理,spring推荐使用TransactionTemplate。

try {
   //TODO something
    transactionManager.commit(status);
} catch (Exception e) {
   transactionManager.rollback(status);
   throw new InvoiceApplyException("异常");
}

声明式事务

  声明式事务是建立在AOP之上的。本质就是使用动态代理对方法的前后进行拦截,实现事务的提交或者回滚。声明式事务的最大优点就是不需要通过编程对事务进行管理。这样就不需要在业务代码中参杂复杂的事务管理的代码。我们只需要通过在配置文件中配置需要添加事务的方法(或者使用@Transactional注解)就能进行事务管理。声明式事务也有两种实现方式,一是基于TX和AOP的xml配置文件方式,二种就是基于@Transactional注解了。

@Transactional
public String user() {
       int insert = userMapper.insert(userInfo);
}

@Transactional用在什么地方?(where)

  @Transactional可以用在接口、类和方法上。

  • 类上:使用在类上的时候,该类的所有public方法上都会配置相同属性的事务。、
  • 方法上:当改方法的类上和方法上都使用了@Transactional时,方法上的事务配置会覆盖类上的事务配置。
  • 接口上:不建议在接口上配置事务,因为当在声明该接口的实现类使用cglib代理时,接口上的事务配置会失效。

@Transactional属性

propagation属性

propagation代表事务的传播行为,默认值为Propagation.REQUIRED。

  • Propagation.REQUIRED:如果当前存在事务,则以当前事务运行。如果当前不存在,则创建一个新的事务运行。
  • Propagation.SUPPORTS:如果当前存在事务,则以当前事务运行。如果不存在事务,则以非事务的方式运行。
  • Propagation.MANDATORY:如果当前存在事务,则以当前事务运行。如果不存在,则抛出异常。
  • Propagation.REQUIRES_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:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与PROPAGATION_REQUIRED类似的操作。
isolation 属性

isolation是事务的隔离级别。默认值为 Isolation.DEFAULT。

  • TransactionDefinition.ISOLATION_DEFAULT:与底层数据库采用的隔离级别保持一致。
  • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:脏读(读未提交)
  • TransactionDefinition.ISOLATION_READ_COMMITTED:不可重复读(读已提交)
  • TransactionDefinition.ISOLATION_REPEATABLE_READ:幻读(范围查询时读入新插入数据或者数据被删除)
  • TransactionDefinition.ISOLATION_SERIALIZABLE:序列化,不存在事务问题。
timeout属性

事务的超时时间,默认值为 -1。如果超过该时间限制但事务还没有完成,则自动回滚事务。

readOnly 属性

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

rollbackFor 属性

用于指定能够触发事务回滚的异常类型,可以指定多个异常类型。

noRollbackFor属性

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

@Transactional什么时候会失效(WHEN)

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

  显然非public方法会返回null。

  1. 数据库引擎不支持事务
      数据库引擎要支持事务,如果是MySQL,注意表要使用支持事务的引擎,比如innodb,如果是myisam,事务是不起作用的。
  2. 事务的Propagation属性配置错误
    配置了Propagation.NOT_SUPPORTED或TransactionDefinition.PROPAGATION_NEVER属性
  3. rollbackFor 设置错误,@Transactional 注解失效
    Spring默认抛出了未检查unchecked异常(继承自 RuntimeException 的异常)或者 Error才回滚事务;其他异常不会触发回滚事务。如果在事务中抛出其他类型的异常,但却期望 Spring 能够回滚事务,就需要指定 rollbackFor属性。
  4. 方法之间的互相调用也会导致@Transactional失效
    例如:一个操作用户的UserService类中有两个public方法A和B,A方法调用B方法,A上没有@Transactional,而B上有。当controller层调用A方法时,B方法上的事务将不会生效。
    原因是由于spring的aop导致的,因为只有当事务方法被当前类以外的代码调用时,才会由Spring生成的代理对象来管理。
  5. 异常被你的 catch“吃了”导致@Transactional失效
    这种情况是最常见的一种@Transactional注解失效场景:
@Transactional
  private Integer A() throws Exception {
      int insert = 0;
      try {
          User tom = new User();
          tom.setCityName("javaHuang");
          tom.setUserId(1);
          User mic = new User();
          mic.setCityName("javaHuang");
          mic.setUserId(1);
          /**
            * 插入user
            */
          insert += userMapper.insert(tom);
          insert += userMapper.insert(mic);
      } catch (Exception e) {
          e.printStackTrace();
      }
      return insert;
  }

如果A方法内部抛了异常,而A方法此时try catch了B方法的异常,那这个事务就不能正常回滚,而是会报出异常。
解决方法:
第一声明事务的时候加上rollback=‘exception’
第二 cath代码块里面手动回滚

总结

以上就是@Transactional的失效场景。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

hi wei

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值