spring事务-传播

前言

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方法里面只要操作了数据库就立马提交。

  1. 先一步步分析,((EnterServiceImpl)(AopContext.currentProxy())).MutiMethod(stu),MutiMethod本身是有事务的,但是不能直接this调用,因为调用本类方法要使用代理对象,spring事务aop获取的是代理对象,直接调用不会走事务。
  2. MutiMethod方法调用localMethod,localMethod方法没有使用代理对象,为什么localMethod还是走了事务,一起提交?。个人理解:调用MutiMethod使用了代理对象,MutiMethod方法里的localMethod也会使用当前代理对象。(延伸:MutiMethod方法调用localMethod时,如果使用代理对象调用localMethod,localMethod不能为私有的,不然代理对象里的成员变量会为空)
  3. enterMethod2调用localMethod时,localMethod方法本身是有事务的(类上配了事务),为啥没走事务?调用本类的方法时,一定要使用代理对象,这里是直接调用的,没走aop的事务,所以这个方法,里面任何操作数据的,都会立马提交到数据库。(小结:调用本类方法时,最外层的一定需要代理对象去调用,这个方法的下层方法还是本类方法时,可不适用代理对象。结合第2点理解)
  4. 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就是代理对象。而代理对象无法访问私有方法,可能是因为访问不到私有方法,导致的没有去设置成员变量 ,具体没看源码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值