Spring事务失效的几种情况,这次清晰了

959 阅读5分钟

1

简述

在日常开发过程中,我们经常会遇到需要使用事务的场景,尤其是微服务盛行的当下,分布式事务也显得尤为重要,据了解很多使用微服务的项目,并没有处理分布式事务……今天不聊分布式事务,先说说我们在单应用的情况下,会有哪些情况导致事务失效呢?

详解

1. 底层数据库引擎不支持事务

以MySQL为例,它有多种引擎,MyISAM引擎不支持事务操作,InnoDB引擎支持事务,MySQL从5.5.5开始默认的引擎是InnoDB,之前的版本默认的都是MyISAM,所以说如果是非常老的项目,这个需要考虑下。

2. 被@Transactional 注解修饰的方法为非public类型

如果被@Transactional注解修饰的方法,修饰符非public或者被final修饰,则事务会失效,因为AOP没办法为这样的方法生成一个代理,自然事务就无法生效。这个在Spring的官方文档里面也有说明。

在这个地方有个详细讨论过程:stackoverflow.com/questions/4…

3. 异常被吃掉了

如果异常被 catch 住,那事务也是会失效呢,伪代码如下:

@Transactional
public void test(){
  try{
    //插入一条数据
    insertA();
    //更改一条数据
    updateB();
  }catch(Exception e){
    log.error("异常被捕获了,事务就失效了",e);
  }
}

4. 异常抛出类型错误

@Transactional 注解有个属性:rollbackFor,这个属性可以设置想要回滚的异常,那它默认的异常是什么?

不要犹豫直接告诉我答案,它默认回滚的是:RuntimeException,如果说我们没有设置这个属性,而且抛出的异常比这个大,那么事务就不会回滚,例如:

@Transactional
public void test(){
  try{
    //插入一条数据
    insertA();
    //更改一条数据
    updateB();
  }catch(Exception e){
    // 这个时候事务就不会回滚
    throw new Exception("操作失败!");
  }
}

5. 本类方法调用

这一个应该是最容易踩坑的了,先来看两段伪代码:

代码一:

@Service
public class TestServiceImpl implements TestService {

    public void testA() {
        // 查询数据,并进行一些判断
        // 调用另外一个方法
        testB();
    }
    
    @Transactional
    public void testB(Test test) {
        // update test
    }
}

问题:testA()方法上面没有加 @Transactional 注解,调用有 @Transactional 注解的 testB() 方法,testB() 方法上的事务管用吗?

代码二:

@Service
public class TestServiceImpl implements TestService {

    @Transactional
    public void testA() {
        // 查询数据,并进行一些判断
        // 调用另外一个方法
        testB();
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void testB(Test test) {
        // update test
    }
}

问题:在 testA() 方法上加了 @TransactionaltestB() 的注解上加了 REQUIRES_NEW 新开启一个事务,那么新开的事务管用么?

既然这么问了,答案其实大家都知道了,这两段伪代码的事务都是不管用的。

因为他们是本类方法直接调用,这个时候会用this关键字,没有经过 Spring 的代理类去调用此方法,从而没有开启事务管理,默认只有在外部调用事务才会生效。

所以说:加事务注解的方法给本类里面的方法调用,事务不生效!

有多种解决方法:

  • 最直白的就是把方法拆出来,放在两个类里面(Spring推荐的一种方式);
  • 在类里面注入自己,用注入的对象再调用另外一个方法,这个不太优雅;
  • 在Spring的配置里面增加一段配置:<aop:aspectj-autoproxy expose-proxy="true"/>

6. 没有被spring管理

当这个类只是一个普通类,没有被spring管理成为一个Bean对象,那它很自然的就不能使用spring提供的事务管理了,事务自然就不生效。例如:把@Service注解去掉,这个时候事务就不会生效,小伙伴们可以试试。

总结

以上列举了几种常见的事务失效的原因,其实发生最多就是异常被吃掉了、异常抛出类型错误、本类方法调用不对这三个了,这三点只要在日常码代码的时候注意一下,基本就不会有问题了。

当然事务失效可能还有其他原因,欢迎私信我一起学习,一起成长!

另外,既然说到了Spring的事务,那就不可不提Spring的事务传播机制,总共有7种,一起复习一下,如下:

  • PROPAGATION_REQUIRED:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。

  • PROPAGATION_SUPPORTS:如果上下文中存在事务则加入当前事务,如果没有事务则以非事务方式执行。

  • PROPAGATION_MANDATORY:该传播级别要求上下文中必须存在事务,否则抛出异常。

  • PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)

  • PROPAGATION_NOT_SUPPORTED:当上下文中有事务则挂起当前事务,执行完当前逻辑后再恢复上下文事务。(降低事务大小,将非核心的执行逻辑包裹执行。)

  • PROPAGATION_NEVER:该传播级别要求上下文中不能存在事务,否则抛出异常。

  • PROPAGATION_NESTED:嵌套事务,如果上下文中存在事务则嵌套执行,如果不存在则新建事务。(save point概念)

有任何问题欢迎微信搜索【Hugh的白板】私信我,一起探讨,一起学习