别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读

时间:2024-01-20 19:50:16



别再跟我说你不理解 @Transactional 原理了!省流版解读

  • 前言
  • user 表初始数据
  • 隔离级别:NESTED 案例一
  • 隔离级别:NESTED 案例一省流版答案解读
  • 隔离级别:NESTED 案例一省流版答案源码入口
  • 隔离级别 Propagation.NESTED 省流版源码剖析
  • 隔离级别:NESTED 案例二
  • 隔离级别:NESTED 案例二省流版答案解读
  • 其他隔离级别:REQUIRED、REQUIRES_NEW
  • 码字还是比较耗时的,后续发布对应的源码级别讲解视频到公众号上


前言

老早前精读过 @Transactional 的源码,现在有时间提炼提炼一下里面的精华出一期,从源码角度剖析 @Transactional 是怎么开启事务的?@Transactional 回滚流程又是怎样的?的角度出发,帮助大家工作中合理的使用 @Transactional 这个注解。

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_回滚



user 表初始数据

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_数据库_02

隔离级别:NESTED 案例一

  1. nestedMethodA 将 id 为 1 的数据的 username 修改 nestedMethodA
  2. nestedMethodB 将 id 为 2 的数据的 username 修改 nestedMethodB
  3. nestedMethodC 将 id 为 3 的数据的 username 修改 nestedMethodC
  4. 最后主事务 将 id 为 4 的数据的 username 修改 mainplus

思考:最后谁能更新成功?????

/**
 * A,B成功,C失败抛出异常进入 catch 逻辑
 */
@Transactional(propagation = Propagation.NESTED)
@Override
public void main() {
    try {
        tabUserService.nestedMethodA();
        tabUserService.nestedMethodB();
        tabUserService.nestedErrorMethodC();
        tabUserService.update(new LambdaUpdateWrapper<TabUser>()
                .eq(TabUser::getId, "4")
                .set(TabUser::getUsername, "mainplus"));
    } catch (Exception e) {
        System.err.println(e.getMessage());
    }
}

隔离级别:NESTED 案例一省流版答案解读

只有 nestedMethodA、nestedMethodB 方法能更新成功

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_回滚_03


原因 NESTED 是基于回滚点回滚事务的,生成的回滚点的位置,如下,当 nestedErrorMethodC 方法抛出异常后,异常进入到 catch 代码块中,导致

tabUserService.update(new LambdaUpdateWrapper<TabUser>()
        .eq(TabUser::getId, "4")
        .set(TabUser::getUsername, "mainplus"))

这段代码没有执行,因此 id 为 4 的数据没有更新。由于 catch 代码块中并没有接着向上抛出此异常,因此没有触发回滚主事务的操作,同时因为 nestedErrorMethodC 抛出异常,触发了回滚到回滚点 C 的逻辑,因此回滚点 C 以下的代码全部被回滚了。而 nestedMethodA、nestedMethodB 事务不受影响,因此

只有 nestedMethodA、nestedMethodB 方法能更新成功

/**
 * A,B成功,C失败抛出异常进入 catch 逻辑
 */
@Transactional(propagation = Propagation.NESTED)
@Override
public void main() {
    //主事务
    try {
        //回滚点 A
        tabUserService.nestedMethodA();
        //回滚点 B
        tabUserService.nestedMethodB();
        //回滚点 C
        tabUserService.nestedErrorMethodC();
        tabUserService.update(new LambdaUpdateWrapper<TabUser>()
                .eq(TabUser::getId, "4")
                .set(TabUser::getUsername, "mainplus"));
    } catch (Exception e) {
        System.err.println(e.getMessage());
    }
}

隔离级别:NESTED 案例一省流版答案源码入口

@Transactional 注解封装了如下三大逻辑

  1. 生成切面,(被 @Transactional 标注的方法的所在类会被生成一个代理对象)
  2. 开启事务,(执行完目标方法前,会先执行被 Spring 封转好的开启事务的逻辑)
  3. 提交事务,(执行完目标方法后,会执行被 Spring 封转好的提交事务的逻辑)
  4. 回滚事务

就拿下面这段代码举例来说,首先会为 TabUserService 生成一个代理对象,@Transactional 注解封装了如下三大逻辑中的点一。

@Service
public class TabUserServiceImpl extends ServiceImpl<TabUserMapper, TabUser> implements TabUserService {
    @Autowired
    private TabUserService tabUserService;

    /**
     * A,B成功,C失败抛出异常进入 catch 逻辑
     */
    @Transactional(propagation = Propagation.NESTED)
    @Override
    public void main() {
        try {
            //回滚点 A
            tabUserService.nestedMethodA();
            //回滚点 B
            tabUserService.nestedMethodB();
            //回滚点 C
            tabUserService.nestedErrorMethodC();
            tabUserService.update(new LambdaUpdateWrapper<TabUser>()
                    .eq(TabUser::getId, "4")
                    .set(TabUser::getUsername, "mainplus"));
        } catch (Exception e) {
            System.err.println(e.getMessage());
        }
    }
}

@Transactional 注解封装了如下三大逻辑中的 点二源码入口

  1. ThredLocal 在 @Transactional 源码中是如何应用的?
  2. @Transactional(propagation = Propagation.NESTED) Spring 事务中的每个隔离级别是如何生效的?
  3. @Transactional 注解设计的思想

    @Transactional 注解封装了如下三大逻辑中的 点三、点四源码入口

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_数据_04

隔离级别 Propagation.NESTED 省流版源码剖析

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_隔离级别_05


回滚点是如何被保存的?源码入口如下:

AbstractPlatformTransactionManager.handleExistingTransaction()

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_回滚_06


如何根据回滚点回滚,源码入口如下

AbstractTransactionStatus.rollbackToHeldSavepoint()

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_回滚_07

隔离级别:NESTED 案例二

nestedMethodA、nestedMethodB、nestedErrorMethodC、主事务均更新失败。

/**
 * A,B成功,C失败抛出异常进入 catch 逻辑
 */
@Transactional(propagation = Propagation.NESTED)
@Override
public void main() {
    //主事务
    try {
        //回滚点 A
        tabUserService.nestedMethodA();
        //回滚点 B
        tabUserService.nestedMethodB();
        //回滚点 C
        tabUserService.nestedErrorMethodC();
        tabUserService.update(new LambdaUpdateWrapper<TabUser>()
                .eq(TabUser::getId, "4")
                .set(TabUser::getUsername, "mainplus"));
    } catch (Exception e) {
        System.err.println(e.getMessage());
        //异常抛到主事务
        throw e;
    }
}

隔离级别:NESTED 案例二省流版答案解读

NESTED 隔离级别下的事务,会和主事务共用一个数据库连接(源码入口如下),虽然 nestedErrorMethodC 方法也是 NESTED 隔离级别,只会回滚自己,但是异常被 catch 捕获后,接着向上抛,抛到了主事务中,导致了主事务回滚了,因为 nestedMethodB、nestedMethodA 与主事务共用一个数据库连接,因此一块回滚了。导致nestedMethodA、nestedMethodB、nestedMethodC连体回滚更新失败。主事务更新失败是因为代码没有得到执行。

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_回滚_08

其他隔离级别:REQUIRED、REQUIRES_NEW

使用上,就字面意思,有时间接着举几个对应的例子,持续更新中~~~~

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_数据库_09

码字还是比较耗时的,后续发布对应的源码级别讲解视频到公众号上

大家可以先关注我一波,或者关注一下公众号,这段时间上班有点忙,后续有时间更新,敬请期待~~~~~~~~

别再跟我说你不理解 @Transactional 为什么会失效了!省流版解读_数据_10