6

面试突击88:加入事务和嵌套事务有什么区别? - Java中文社群

 1 year ago
source link: https://www.cnblogs.com/vipstone/p/16760086.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

加入事务和嵌套事务是指在 Spring 事务传播机制中的加入事务(REQUIRED)和嵌套事务(NESTED)的区别,二者看似很像,实则截然不同,那么它们有什么区别呢?接下来我们一起来看。

Spring 事务传播机制是指,包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的,Spring 事务传播机制分为 3 大类,总共 7 种级别,如下图所示:

其中,支持当前事务的 REQUIRED 是加入(当前)事务,而 NESTED 是嵌套(当前)事务,本文要讨论的就是这二者的区别。

1.加入事务

加入事务 REQUIRED 是 Spring 事务的默认传播级别。

所谓的加入当前事务,是指如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。我们这里重点要讨论的是第一种情况,也就是当前存在事务的情况下,它和嵌套事务的区别,接下来我们通过一个示例来看加入事务的使用和执行特点。

我们要实现的是用户添加功能,只不过在添加用户时,我们需要给用户表和日志表中分别插入一条数据,UserController 实现代码如下:

@Transactional(propagation = Propagation.REQUIRED)
@RequestMapping("/add")
public int add(UserInfo userInfo) {
    int result = 0;
    int userResult = userService.add(userInfo);
    System.out.println("用户添加结果:" + userResult);
    if (userResult > 0) {
        LogInfo logInfo = new LogInfo();
        logInfo.setName("添加用户");
        logInfo.setDesc("添加用户结果:" + userResult);
        int logResult = logService.add(logInfo);
        System.out.println("日志添加结果:" + logResult);
        result = 1;
    }
    return result;
}

从上述代码可以看出,添加用户使用了事务,并设置了事务传播机制为 REQUIRED(加入事务),此控制器调用的 UserService 实现代码如下:

@Transactional(propagation = Propagation.REQUIRED)
public int add(UserInfo userInfo) {
    int result = userMapper.add(userInfo);
    return result;
}

从上述代码可以看出,它也是使用事务,并设置了事务的传播机制为 REQUIRED,而 LogService 也是类似的实现代码:

@Transactional(propagation = Propagation.REQUIRED)
public int add(LogInfo logInfo) {
    int result = logMapper.add(logInfo);
    try {
        int number = 10 / 0;
    } catch (Exception e) {
        // 手动回滚事务
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return result;
}

从上述代码我们可以看出,在设置事务传播机制的同时,我们也在程序中主动的设置了一个异常。

运行以上程序的执行结果如下图所示:

从上述结果我们可以看出:当我们设置了加入事务的事务传播机制之后,程序的执行结果是将用户表和日志表的事务都回滚了

2.嵌套事务

嵌套事务指的是事务传播级别中的 NESTED,所谓的嵌套当前事务,是指如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于 REQUIRED。当然,我们本文要研究的重点也是第一种情况,也就是当前存在事务的前提下,嵌套事务和加入事务的区别。

所以接下来我们将上面代码中的事务传播机制改为 NESTED,它的实现代码如下。
UserController 实现代码如下:

@Transactional(propagation = Propagation.NESTED)
@RequestMapping("/add")
public int add(UserInfo userInfo) {
    int result = 0;
    int userResult = userService.add(userInfo);
    System.out.println("用户添加结果:" + userResult);
    if (userResult > 0) {
        LogInfo logInfo = new LogInfo();
        logInfo.setName("添加用户");
        logInfo.setDesc("添加用户结果:" + userResult);
        int logResult = logService.add(logInfo);
        System.out.println("日志添加结果:" + logResult);
        result = 1;
    }
    return result;
}

UserService 实现代码如下:

@Transactional(propagation = Propagation.NESTED)
public int add(UserInfo userInfo) {
    int result = userMapper.add(userInfo);
    return result;
}

LogService 实现代码如下:

@Transactional(propagation = Propagation.NESTED)
public int add(LogInfo logInfo) {
    int result = logMapper.add(logInfo);
    try {
        int number = 10 / 0;
    } catch (Exception e) {
        // 手动回滚事务
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
    return result;
}

运行以上程序的执行结果如下图所示:

从上述结果可以看出:当设置嵌套事务的事务传播级别之后,程序执行了部分事务的回滚,用户表添加的事务没有回滚,只是日志表的事务回滚了。

3.加入事务 VS 嵌套事务

加入事务(REQUIRED)和嵌套事务(NESTED)都是事务传播机制的两种传播级别,如果当前不存在事务,那么二者的行为是一样的;但如果当前存在事务,那么加入事务的事务传播级别在遇到异常之后,会将事务全部回滚;而嵌套事务在遇到异常时,只是执行了部分事务的回滚。

4.嵌套事务实现原理

事务全部回滚很好理解,这本来就是事务原子性的一种体现,而嵌套事务中的部分事务回滚是怎么实现的呢?

嵌套事务只所以能实现部分事务的回滚,是因为在数据库中存在一个保存点(savepoint)的概念,以 MySQL 为例,嵌套事务相当于新建了一个保存点,而滚回时只回滚到当前保存点,因此之前的事务是不受影响的,这一点可以在 MySQL 的官方文档汇总找到相应的资料:https://dev.mysql.com/doc/refman/5.7/en/savepoint.html

而 REQUIRED 是加入到当前事务中,并没有创建事务的保存点,因此出现了回滚就是整个事务回滚,这就是嵌套事务和加入事务的区别。

保存点就像玩通关游戏时的“游戏存档”一样,如果设置了游戏存档,那么即使当前关卡失败了,也能继续上一个存档点继续玩,而不是从头开始玩游戏。

加入事务(REQUIRED)和嵌套事务(NESTED)都是事务传播机制中的两种传播级别,如果当前不存在事务,那么二者的行为是一致的;但如果当前存在事务,那么加入事务的事务传播级别当遇到异常时会回滚全部事务,而嵌套事务则是回滚部分事务。嵌套事务之所以能回滚部分事务,是因为数据库中存在一个保存点的概念,嵌套事务相对于新建了一个保存点,如果出现异常了,那么只需要回滚到保存点即可,这样就实现了部分事务的回滚。

是非审之于己,毁誉听之于人,得失安之于数。

公众号:Java面试真题解析

面试合集:https://gitee.com/mydb/interview


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK