【ⓈSpring & Spring MVC】Spring事务失效的场景有哪些?如何解决?

实际项目开发中,如果涉及到多张表操作时,为了保证业务数据的一致性,大家一般都会采用事务机制。此篇文章给大家整理了一下常见Spring事务失效的场景。

常见的失效场景:

  • 注解@Transactional配置的方法非public权限修饰;
  • 注解@Transactional所在类非Spring容器管理的bean
  • 注解@Transactional所在类中,注解修饰的方法被类内部方法调用
  • 业务代码抛出异常类型非RuntimeException,事务失效;
  • 业务代码中存在异常时,使用try…catch…语句块捕获,而catch语句块没有throw new RuntimeExecption异常;(最难被排查到问题且容易忽略)
  • 注解@Transactional中Propagation属性值设置错误即Propagation.NOT_SUPPORTED(一般不会设置此种传播机制)
  • mysql关系型数据库,且存储引擎是MyISAM而非InnoDB,则事务会不起作用(基本开发中不会遇到);

非public权限修饰

Spring官方文档:

使用代理时,您应该只将@Transactional注释应用于具有公共可见性的方法。如果使用@Transactional注释对受保护的、私有的或包可见的方法进行注释,则不会引发错误,但带注释的方法不会显示配置的事务设置。如果需要注释非公共方法,请考虑使用AspectJ

简言之:@Transactional 只能用于 public 的方法上,否则事务不会失效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。

目前,如果@Transactional注解作用在非public方法上,编译器也会给与明显的提示。

非Spring容器管理的bean

一般在开发中不会。

注解修饰的方法被类内部方法调用

这种失效场景是我们日常开发中最常踩坑的地方;在类A里面有方法a 和方法b, 然后方法b上面用 @Transactional加了方法级别的事务,在方法a里面 调用了方法b, 方法b里面的事务不会生效。为什么会失效呢?

原因很简单,Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,实际上是代理类调用的,代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B(),此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效

@Service
public class ClassServiceImpl implements ClassService {

    @Autowired
    private ClassMapper classMapper;

    public void insertClass(ClassDo classDo) throws CustomException {
        insertClassByException(classDo);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertClassByException(ClassDo classDo) throws CustomException {
        classMapper.insertClass(classDo);
        throw new RuntimeException();
    }
}

解决方案

类内部使用其代理类调用事务方法:以上方法略作改动

public void insertClass(ClassDo classDo) throws CustomException {
    //         insertClassByException(classDo);
    ((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);
}
//测试用例:
@Test
public void insertInnerExceptionTest() throws CustomException {
    classDo.setClassId(3);
    classDo.setClassName("java_3");
    classDo.setClassNo("java_3");

    classService.insertClass(classDo);
}

注意:一定要注意启动类上要添加@EnableAspectJAutoProxy(exposeProxy = true)注解,否则启动报错

异常类型非RuntimeException

@Transactional默认只捕捉RuntimeException异常

解决方案

@Transactional注解修饰的方法,加上rollbackfor属性值,指定回滚异常类型:@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)

捕获异常后,却未抛出异常

在事务方法中使用try-catch,导致异常无法抛出,自然会导致事务失效。

解决方案

捕获异常并抛出异常

事务传播行为设置异常

此种事务传播行为不是特殊自定义设置,基本上不会使用Propagation.NOT_SUPPORTED,不支持事务

数据库存储引擎不支持事务

以MySQL关系型数据为例,如果其存储引擎设置为 MyISAM,则事务失效,因为MyISMA 引擎是不支持事务操作的;

故若要事务生效,则需要设置存储引擎为InnoDB ;目前 MySQL 从5.5.5版本开始默认存储引擎是:InnoDB;

 

参考:

 

posted @ 2023-03-25 21:41  残城碎梦  阅读(37)  评论(0编辑  收藏  举报