您的当前位置:首页正文

spring事务及失效场景及具体分析

2024-11-11 来源:个人技术集锦

目录

概述

方法上加上@Transactional

在使用 Spring 事务时不能使用 try-catch 进行异常捕获,要将异常抛给外层,使其进行异常拦截,触发事务机制。

问题

失效场景

抛出检查型异常时事务失效,即写try catch异常失效
Exception 受检查的异常:在程序中必须使用 try…catch 进行处理,遇到这种异常不处理,编译器会报错。例如 IOException 。

如果想实现只要抛出异常就回滚,可以通过添加注解 @Transactional(rollbackFor=Exception.class)
实现。

2.一个事务方法调用另一个事务方法时失效
因为Spring 的声明式事务使用了代理。此时必须指定事务应该如何传播,也就是传播行为
事务传播行为是为了解决业务层方法之间互相调用的事务问题

共有七种
@Transactional(propagation = Propagation.REQUIRED)
@Transactional(propagation = Propagation.REQUIRES_NEW)

补充说明:
MySQL 且引擎是 MyISAM,则事务会不起作用,原因是 MyISAM 不支持事务,改成 InnoDB 引擎则支持事务
注解 @Trasactional 只能加在 public 修饰的方法上事务才起效。如果加在 protect、private 等非
public 修饰的方法上,事务将失效。
不同类之间方法调用时,异常发生在无事务的方法A中,但不是被调用的方法B产生的,被调用的方法B的事务无效。

解决方法:

具体的失效场景:
注解@Transactional配置的方法非public权限修饰;
注解@Transactional所在类非Spring容器管理的bean;
注解@Transactional所在类中,注解修饰的方法被类内部方法调用;
业务代码抛出异常类型非RuntimeException,事务失效;
业务代码中存在异常时,使用try…catch…语句块捕获,而catch语句块没有throw new RuntimeExecption异常;(最难被排查到问题且容易忽略)
注解@Transactional中Propagation属性值设置错误即Propagation.NOT_SUPPORTED(一般不会设置此种传播机制)

数据库层面:
mysql关系型数据库,且存储引擎是MyISAM而非InnoDB,则事务会不起作用(基本开发中不会遇到);

基于以上的场景:

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

非Spring容器管理的bean
基于这种失效场景,有工作经验的大佬基本上是不会存在这种错误的;@Service 注解注释,StudentServiceImpl 类则不会被Spring容器管理,因此即使方法被@Transactional注解修饰,事务也亦然不会生效。

/**
 * @Author:qxy
 */
//@Service
public class StudentServiceImpl implements StudentService {
 
    @Autowired
    private StudentMapper studentMapper;
 
    @Autowired
    private ClassService classService;
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
    public void insertClassByException(StudentDo studentDo) throws CustomException {
        studentMapper.insertStudent(studentDo);
        throw new CustomException();
    }
}

注解修饰的方法被类内部调用的方法
这种场景是我们的日常开发最常才坑的地方:在A里面有方法和方法b,然后方法b上面用@Transactional增加了方法级别的事务,在a方法里面调用了方法b,A 调用了B方法,方法b里面的事务不会生效。为什么会失效呢?
其实原因很简单,Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy)其实原因很简单,Spring在扫描Bean的时候会自动为标注了@Transactional注解的类生成一个代理类(proxy),当有注解的方法被调用的时候,
代理类在调用之前会开启事务,执行事务的操作,但是同类中的方法互相调用,相当于this.B()
此时的B方法并非是代理类调用,而是直接通过原有的Bean直接调用,所以注解会失效

异常类型非RuntimeException

@Service
public class ClassServiceImpl implements ClassService {
 
    @Autowired
    private ClassMapper classMapper;
 
//    @Override
//    @Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
    public void insertClass(ClassDo classDo) throws Exception {
//        即使此处使用代理对象调用内部事务方法,数据依然未发生回滚,事务机制亦然失效
        ((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void insertClassByException(ClassDo classDo) throws Exception {
        classMapper.insertClass(classDo);
        //抛出非RuntimeException类型
        throw new Exception();
    }
//测试用例:
 @Test
    public void insertInnerExceptionTest() throws Exception {
       classDo.setClassId(3);
       classDo.setClassName("java_3");
       classDo.setClassNo("java_3");
 
       classService.insertClass(classDo);
    }
}

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

@Service
public class ClassServiceImpl implements ClassService {
 
    @Autowired
    private ClassMapper classMapper;
 
//    @Override
    public void insertClass(ClassDo classDo) {
        ((ClassServiceImpl)AopContext.currentProxy()).insertClassByException(classDo);
 
    }
 
    @Override
    @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) {
        classMapper.insertClass(classDo);
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 
// 测试用例:
 @Test
    public void insertInnerExceptionTest() {
       classDo.setClassId(4);
       classDo.setClassName("java_4");
       classDo.setClassNo("java_4");
 
       classService.insertClass(classDo);
    }

事务传播行为设置异常

 @Transactional(propagation = Propagation.NOT_SUPPORTED,rollbackFor = Exception.class)
    public void insertClassByException(ClassDo classDo) {
        classMapper.insertClass(classDo);
        try {
            int i = 1 / 0;
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

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

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

实现思路分析

相关工具如下:

分析:

小结:

主要讲述了Spring编程式和声明式事务实例讲解
剖析, 里面有许多不足,请大家指正~

参考资料和推荐阅读

1.链接: .

1.链接: .

显示全文