前言
service事务经常用,但都是按照网上的一些文章总结的东西去使用的,兴趣来了自己写个demo代码,测试下事务的几个场景。demo里面使用 REQUIRED和 NOT_SUPPORTED两个事务传播。自己写demo解惑,也发现一些跟网上说的不一样的地方。(比如调用本类方法想走事务,一定要用代理对象,其实不是,只要上层的用了就可以,下层的可以不再使用代理对象,也能走事务)
7种事务传播
事务传播 | 事务传播 介绍 |
---|---|
PROPAGATION_REQUIRED | 支持当前事务,如果当前没有事务,就新建一个事务。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行 |
PROPAGATION_MANDATORY | 支持当前事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 嵌套事务。 |
前面6种都很好理解,这里再多讲下嵌套事务PROPAGATION_NESTED :
1.嵌套事务PROPAGATION_NESTED 则是外部事务的子事务, 外部事务 commit, 嵌套事务才会被 commit, 这个规则同样适用于回滚,外层事务回滚,嵌套事务也会回滚.
2.外层事务的回滚会引起内层嵌套事务的回滚。而内层嵌套事务的异常并不会导致外层事务的回滚,它是一个真正的嵌套事务。
3.PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 的最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 嵌套事务也会被 commit, 这个规则同样适用于 roll back.
代码
7种事务,选支持事务的REQUIRED和不支持事务的NOT_SUPPORTED测试。先贴上测试事务的代码,后面再讲结论
//实体类
public class StudentDto implements Serializable {
/**
*
*/
private static final long serialVersionUID = -3224736548682275561L;
private String name ;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
public interface SpecialService {
public void specialMethod(StudentDto stu);
}
//没有申明事务的service,specialMethod方法也没申明事务
@Service
public class SpecialServiceImpl implements SpecialService {
@Autowired
CommonService commonService;
@Autowired
Special2Service special2Service;
@Override
public void specialMethod(StudentDto stu){
commonService.saveCommon(stu);
special2Service.specialMethod2(stu);
};
}
public interface Special2Service {
public void specialMethod2(StudentDto stu);
}
//没有申明事务的service,specialMethod2方法也没申明事务
@Service
public class Special2ServiceImpl implements Special2Service{
@Autowired
CommonService commonService;
@Override
public void specialMethod2(StudentDto stu){
commonService.saveCommon(stu);
}
}
public interface CommonService {
public void saveCommon(StudentDto stu);
}
//类上申明了事务,会应用到方法上,saveCommon有事务
@Service
@Transactional
public class CommonServiceImpl implements CommonService {
@Override
public void saveCommon(StudentDto stu){
// dao层插入数据 , 代码忽略
}
}
public interface EnterService {
public void enterMethod1(StudentDto stu);
public void enterMethod2(StudentDto stu);
}
//proxyTargetClass=true:基于类的代理将起作用(走cglib)。默认false走接口的动态代理
//exposeProxy=true:用于支持自调用,使用AopContext.currentProxy()获取当前代理对象
@Service
@Transactional
@EnableAspectJAutoProxy(proxyTargetClass=true,exposeProxy=true)
public class EnterServiceImpl implements EnterService{
@Autowired
CommonService CommonService;
@Autowired
SpecialService specialService;
//类上有事务,这里也会有事务,默认 REQUIRED
@Override
public void enterMethod1(StudentDto stu) {
CommonService.saveCommon(stu);
CommonService.saveCommon(stu);
specialService.specialMethod(stu);
}
@Transactional(propagation=Propagation.NOT_SUPPORTED)
@Override
public void enterMethod2(StudentDto stu) {
//代理对象调用的,这个方法MutiMethod本身有事务,全部成功或者全部失败
((EnterServiceImpl)(AopContext.currentProxy())).MutiMethod(stu);
localMethod(stu);
specialService.specialMethod(stu);
}
private void localMethod(StudentDto stu){
CommonService.saveCommon(stu);
CommonService.saveCommon(stu);
}
public void MutiMethod(StudentDto stu){
CommonService.saveCommon(stu);
CommonService.saveCommon(stu);
localMethod(stu);
}
}
@RestController
@RequestMapping("/enter")
public class EnterController {
@Autowired
private EnterService enterService;
@PostMapping("/testTransactional")
public String testTransactional(@RequestBody StudentDto stu ) throws Exception {
enterService.enterMethod1(stu);
//enterService.enterMethod2(stu);
return "成功";
}
}
测试现象,testTransactional方法调用enterMethod1时,是在所有代码执行完才提交事务。
前面enterMethod1的事务传播是REQUIRED,saveCommon事务传播也是REQUIRED,连续调用saveCommon两次走一个事务是很容易理解的。但是specialMethod方法没有申明事务的,现在也走了同一个事务,这说明当方法没有申明事务传播的时候,会使用上层方法的事务作为本方法的事务。那如果方法上显示申明了事务传播,那么会怎么样。经过试验,调整specialMethod的事务传播为REQUIRES_NEW时,specialMethod方法会立马提交事务,之后enterMethod1再次提交事务。
测试现象,testTransactional方法调用enterMethod2时,MutiMethod方法执行完提交事务,localMethod和specialMethod方法里面只要操作了数据库就立马提交。
- 先一步步分析,((EnterServiceImpl)(AopContext.currentProxy())).MutiMethod(stu),MutiMethod本身是有事务的,但是不能直接this调用,因为调用本类方法要使用代理对象,spring事务aop获取的是代理对象,直接调用不会走事务。
- MutiMethod方法调用localMethod,localMethod方法没有使用代理对象,为什么localMethod还是走了事务,一起提交?。个人理解:调用MutiMethod使用了代理对象,MutiMethod方法里的localMethod也会使用当前代理对象。(延伸:MutiMethod方法调用localMethod时,如果使用代理对象调用localMethod,localMethod不能为私有的,不然代理对象里的成员变量会为空)
- enterMethod2调用localMethod时,localMethod方法本身是有事务的(类上配了事务),为啥没走事务?调用本类的方法时,一定要使用代理对象,这里是直接调用的,没走aop的事务,所以这个方法,里面任何操作数据的,都会立马提交到数据库。(小结:调用本类方法时,最外层的一定需要代理对象去调用,这个方法的下层方法还是本类方法时,可不适用代理对象。结合第2点理解)
- specialService.specialMethod(stu)本身是没有申明事务的,外层方法enterMethod2是NOT_SUPPORTED,所以specialService.specialMethod(stu)也不走事务。
事务小结
1.如果最上层方法有事务,那么不管被调用的下层的方法事务是什么情况,最上层方法的事务都要在执行完所有代码后才会提交事务。(比如下层方法有事务则外层的事务挂起,外层的事务总是在所有代码执行完之后提交)
2.如果外层方法有事务,下层方法没有事务,则下层方法的事务申明跟上层的一致。除非下层方法显示的申明有事务或者无事务,这时就以下层申明的事务为准。(适用所有场景,同个service的私有方法或者不同类的service方法也适用)
3。如果外层无事务,本类的下层的方法(本身有事务的情况)想要有事务,需要使用代理对象去调用。如果下层方法是非本类的,调用对象都是注入进来的,会有代理。
4。如果代理对象调用的方法是私有的,那么代理对象里的成员对象会是空的。所以代理对象要访问的方法只能是 默认 protect 或者public级别的。
疑问
第2点与第4点看起来有点冲突。代理对象调用本类的非私有的方法a时,a方法又调用本类私有方法b,a会把事务传递给b。但是直接用代理对象调用b时,代理对象的成员变量会是空导致调用报错。
个人理解是,a会传给b,可能这时的this就是代理对象。而代理对象无法访问私有方法,可能是因为访问不到私有方法,导致的没有去设置成员变量 ,具体没看源码。