事务二.spring事务传播

文章总结

  • 遇到多重事务问题时,如果不确定最好功能测试下验证结果.分析可以通过伪代码一层层分析.
  • 事务传播属性是注解性事务特有的,第二个事务才会用到该属性
  • 主要注意几点,抛异常代码不会往下走,只走catch和finally,源码中newTransaction很大程度上左右事务是否真正提交或回滚,此外还有rollbackOnly
  • 事务是与线程绑定的,存储在ThreadLocal里
  • required_new是完全创建一个新事务,解除旧ThreadLocal的绑定,自己执行完又恢复旧的事务数据
  • nest是创建回滚点,异常时按照回滚点提交后抛异常

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

测试代码

在AllService中,事务方法save()调用了另外两个类的事务方法userService.saveUser(),cityservice.saveCity()

@Service
public class AllService {
    @Autowired
    private UserService userService;
    @Autowired
    private Cityservice cityservice;

    @Transactional
    public String save(){
        int i = userService.saveUser();
        int i1 = cityservice.saveCity();
        System.out.println(this);
        return ((i==1?"User ":"")+(i1==1?"City":"")+" success");
    }
}
@Service
public class Cityservice {

    @Autowired
    private ServiceMapper mapper;

    @Transactional
    public int saveCity(){
        City city = new City();
        city.setName("德玛西亚");
        city.setNum("864523");
        return mapper.saveCity(city);
    }
}
@Service
public class UserService {
    @Autowired
    private ServiceMapper mapper;

    @Transactional
    public int saveUser(){
        User user = new User();
        user.setAge("100");
        user.setName("派克");
        user.setIntroduction("水鬼");
        return mapper.saveUser(user);
    }
}
public interface ServiceMapper {
    @Insert("insert into city(name, num) values(#{name}, #{num})")
    int saveCity(City c);
    @Insert("insert into user(name, age, introduction) values(#{name}, #{age}, #{introduction})")
    int saveUser(User u);
}

事务代理源码

TransactionInterceptor.class的invoke(),最终走下面逻辑
在这里插入图片描述

AllService事务代理伪代码

注意catch住异常后会继续抛异常throw ex

public String save(){
    代理开启事务createTransactionIfnecessary
    try {
        int i = userService.saveUser();
        int i1 = cityservice.saveCity();
    }catch (Throwable ex){
        代理事务回滚
        throw ex;
    }
    代理事务提交commitTransactionAfterReturning
}

继续替换伪代码

public String save(){
    代理开启事务createTransactionIfnecessary
    try {
        
=====================================cityservice.saveUser()开始===========================================
    	代理开启事务createTransactionIfnecessary
    	try {
        	     saveUser()真实代码逻辑;
    	}catch (Throwable ex){
        	    代理事务回滚
        	    throw ex;
    	}
    	代理事务提交commitTransactionAfterReturning
=====================================cityservice.saveUser()结束===========================================

=====================================cityservice.saveCity()开始===========================================
    	代理开启事务createTransactionIfnecessary
    	try {
        	     saveCity()真实代码逻辑;
    	}catch (Throwable ex){
        	    代理事务回滚
        	    throw ex;
    	}
=====================================cityservice.saveCity()结束===========================================

    }catch (Throwable ex){
        代理事务回滚
        throw ex;
    }
    代理事务提交commitTransactionAfterReturning
}

spring传播属性

在这里插入图片描述

事务传播案例

正常流程

获取连接对象

@Autowired
private DataSource dataSource;

@Transactional
public String save(){
    ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    System.out.println(connectionHolder);//打印出连接对象验证是否同一连接对象,mybatis也是这种方法获取连接对象
    ....
}

事务源码分析

AllService.save()第一个开启事务流程如上篇博客源码流程一样, 执行到userService.saveUser()第二次开启事务则有部分不同

  • 连接对象是从ThreaLocal里获取的, 事务对象newConnectionHolder为false
    在这里插入图片描述
  • 根据传播属性(也就是说只有第二个事务及以后才会用到传播属性)修改部分参数, newTransaction为false,
    该标识意味着该事务对象不能回滚和提交, 得等待跟随最外层事务对象决定回滚和提交
    DataSourceTransactionManager.class在这里插入图片描述
    AbstractPlatformTransactionManager.class
    在这里插入图片描述
    在这里插入图片描述
  • 提交时,newTransaction为false,所以738行不会进,不提交事务,回滚时831行也不会进,不回滚
    在这里插入图片描述
    在这里插入图片描述
    cityservice.saveCity()第三次开启事务与第二次一样

小结

在这里插入图片描述

saveUser()抛异常

@Transactional
public int saveUser(){
    ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    System.out.println(connectionHolder);
    User user = new User();
    user.setAge("100");
    user.setName("派克");
    user.setIntroduction("水鬼");
    int i = mapper.saveUser(user);
    if (true) throw new RuntimeException();//异常
    return i;
}

小结

在这里插入图片描述

saveCity()抛异常

@Transactional
public int saveCity(){
    ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
    System.out.println(connectionHolder);
    City city = new City();
    city.setName("德玛西亚");
    city.setNum("864523");
    int i = mapper.saveCity(city);
    if (true) throw new RuntimeException();//异常
    return i;
}

小结

在这里插入图片描述

saveCity()抛异常, save()try-catch------坑

@Transactional
public String save(){
    try {
        ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        System.out.println(connectionHolder);
        int i = userService.saveUser();
        int i1 = cityservice.saveCity();
        System.out.println(this);
        return ((i==1?"User ":"")+(i1==1?"City":"")+" success");
    }catch (Exception e){
        System.out.println("我吃掉异常,不往外抛");
        return "false";
    }
}

从下面伪代码看, 由于26行会吃掉异常,导致30行捕获不到异常,事务正常提交,应该都成功, 但是结果所有都失败

public String save(){
    代理开启事务createTransactionIfnecessary
    try {
        真实业务try{
=====================================cityservice.saveUser()开始===========================================
    	代理开启事务createTransactionIfnecessary
    	try {
真实业务        saveUser()真实代码逻辑;
    	}catch (Throwable ex){
        	    代理事务回滚
        	    throw ex;
    	}
    	代理事务提交commitTransactionAfterReturning
=====================================cityservice.saveUser()结束===========================================

=====================================cityservice.saveCity()开始===========================================
    	代理开启事务createTransactionIfnecessary
    	try {
真实业务        saveCity()真实代码逻辑;
    	}catch (Throwable ex){
        	    代理事务回滚
        	    throw ex;
    	}
=====================================cityservice.saveCity()结束===========================================
真实业务        }catch (Throwable ex){
真实业务        	System.out.println("我吃掉异常,不往外抛");
真实业务        }
        
        
    }catch (Throwable ex){
        代理事务回滚
        throw ex;
    }
    代理事务提交commitTransactionAfterReturning
}

源码分析

saveCity()抛异常时,在回滚时,虽然不回滚代码,但设置了rollbackOnly属性为true,该标识意味着不能提交,只能回滚
AbstractPlatformTransactionManager.calss
在这里插入图片描述DataSourceTransactionManager.class
在这里插入图片描述
save()由于被真实业务代码捕获异常,导致事务代码没捕获异常,正常提交,走提交代码时会判断rollbackOnly,最终走回滚代码
AbstractPlatformTransactionManager.class
在这里插入图片描述
可能spring为了防止你catch后忘记抛异常吧,所以伪代码的注意点如下

public String save(){
    代理开启事务createTransactionIfnecessary,
        /根据ThreaLocal有无连接对象,判断是否新建连接对象,标识newConnectionHolder为truefalse/
        /根据是否是第一个事务和传播属性,标识newTransaction为truefalse/
    try {
        int i = userService.saveUser();
        int i1 = cityservice.saveCity();
    }catch (Throwable ex){
        代理事务回滚
        	/设置rollbackOnly为true/
        	/newTransaction如果false,不回滚/
        throw ex;
    }
    代理事务提交commitTransactionAfterReturning
          /newTransaction如果false,不提交/
          /rollbackOnly为true,走回滚代码/
}

小结

在这里插入图片描述

saveCity()抛异常并且()try-catch

    @Transactional
    public int saveCity(){
        int i = 0;
        try {
            ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
            System.out.println(connectionHolder);
            City city = new City();
            city.setName("德玛西亚");
            city.setNum("864523");
            i = mapper.saveCity(city);
            if (true) throw new RuntimeException();
        }catch (Exception e){
            System.out.println("我吃掉异常,不往外抛");
            return i;
        }
        return i;
    }
}

小结

在这里插入图片描述

saveUser()传播属性new

AllService.save()第一个开启事务流程如上篇博客源码流程一样, 执行到userService.saveUser()第二次开启事务则有部分不同

源码分析

在这里插入图片描述

在这里插入图片描述

  • 挂起逻辑,保存事务管理器的属性和ThreadLocal的绑定后清空
    在这里插入图片描述
    在这里插入图片描述
  • 又回到开启新事务的逻辑
    在这里插入图片描述
  • 由于是新事务,可以提交,然后走finally逻辑,将旧的事务恢复,恢复事务管理器的属性,恢复ThreadLocal的绑定
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

小结

在这里插入图片描述

saveUser()传播属性nested,saveCity()传播属性nested并且抛异常------坑

  • nested就是回滚点的意思,回滚点设计是希望回滚时按照回滚点回滚,所以按常理说saveCity抛异常,应该只回滚saveCity,但结果是全部回滚

源码分析

AllService.save()第一个开启事务流程如上篇博客源码流程一样, 执行到userService.saveUser()第二次开启事务则有部分不同
在这里插入图片描述
创建回滚点
在这里插入图片描述
ConnectionHolder.class
在这里插入图片描述
提交commitTransactionAfterReturning时,在判断newTransaction前会去释放去掉回滚点
在这里插入图片描述
在这里插入图片描述
第三次开启事务和第二次一样,但由于抛了异常,走回滚逻辑completeTransactionAfterThrowing,在判断newTransaction前会回滚到混滚点,然后释放去掉回滚点,抛异常
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

小结

在这里插入图片描述

save()try-catch,saveUser()传播属性nested,saveCity()传播属性nested并且抛异常

@Transactional
public String save(){
    try {
        ConnectionHolder connectionHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
        System.out.println(connectionHolder);
        int i = userService.saveUser();
        int i1 = cityservice.saveCity();
        System.out.println(this);
        return ((i==1?"User ":"")+(i1==1?"City":"")+" success");
    }catch (Exception e){
        System.out.println("我吃掉异常,不往外抛");
        return "false";
    }
}

小结

在这里插入图片描述

事务传播级别	抛异常	业务逻辑catch异常	提交流程	最终结果

save() required默认 catch 新建连接,saveCity()异常被真实业务代码catch,事务没catch到,走提交逻辑,提交时saveUser()一起提交 提交
saveUser() nested 同一个连接,创建userSavepoint,走committTAR,删除userSavepoint,由于newTransaction为false,commitTAR时不提交 提交
saveCity() nested RuntimeException 同一个连接,创建citySavepoint,捕获到异常,走回滚逻辑,回滚到回滚点citySavepoint,删除citySavepoint,再往外抛异常 回滚

save()try-catch,saveUser()抛异常,saveCity()传播属性nested并且抛异常

小结

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值