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

LD is tigger forever,CG are not brothers forever, throw the pot and shine forever.
Modesty is not false, solid is not naive, treacherous but not deceitful, stay with good people, and stay away from poor people.
talk is cheap, show others the code,Keep progress,make a better result.
Survive during the day and develop at night。

目录

概述

方法上加上@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.链接: 参考资料.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

执于代码

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

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

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

打赏作者

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

抵扣说明:

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

余额充值