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