乐趣区

关于java:面试突击87说一下-Spring-事务传播机制

Spring 事务流传机制是指,蕴含多个事务的办法在互相调用时,事务是如何在这些办法间流传的。

既然是“事务流传”,所以事务的数量应该在两个或两个以上,Spring 事务流传机制的诞生是为了规定多个事务在流传过程中的行为的。比方办法 A 开启了事务,而在执行过程中又调用了开启事务的 B 办法,那么 B 办法的事务是应该退出到 A 事务当中呢?还是两个事务互相执行互不影响,又或者是将 B 事务嵌套到 A 事务中执行呢?所以这个时候就须要一个机制来规定和束缚这两个事务的行为,这就是 Spring 事务流传机制所解决的问题。

Spring 事务流传机制有哪些?

Spring 事务流传机制可应用 @Transactional(propagation=Propagation.REQUIRED) 来定义,Spring 事务流传机制的级别蕴含以下 7 种:

  1. Propagation.REQUIRED:默认的事务流传级别,它示意如果以后存在事务,则退出该事务;如果以后没有事务,则创立一个新的事务。
  2. Propagation.SUPPORTS:如果以后存在事务,则退出该事务;如果以后没有事务,则以非事务的形式持续运行。
  3. Propagation.MANDATORY:(mandatory:强制性)如果以后存在事务,则退出该事务;如果以后没有事务,则抛出异样。
  4. Propagation.REQUIRES_NEW:示意创立一个新的事务,如果以后存在事务,则把以后事务挂起。也就是说不论内部办法是否开启事务,Propagation.REQUIRES_NEW 润饰的外部办法会新开启本人的事务,且开启的事务互相独立,互不烦扰。
  5. Propagation.NOT_SUPPORTED:以非事务形式运行,如果以后存在事务,则把以后事务挂起。
  6. Propagation.NEVER:以非事务形式运行,如果以后存在事务,则抛出异样。
  7. Propagation.NESTED:如果以后存在事务,则创立一个事务作为以后事务的嵌套事务来运行;如果以后没有事务,则该取值等价于 PROPAGATION_REQUIRED。

以上 7 种流传机制,可依据“是否反对以后事务”的维度分为以下 3 类:

看到这里,有人可能会说:说了这么多,我也看不懂啊,即便看懂了,我也记不住啊?这要咋整?

没关系,接下来咱们用一个例子,来阐明这 3 类事务流传机制的区别。

以情侣之间是否要买房为例,咱们将以上 3 类事务流传机制能够看作是恋爱中的 3 类女生类型:

  • 普通型
  • 强势型
  • 懂事型

这三类女生如下图所示:

反对以后事务的“女生”,这里的事务指的是“房子”,它分为 3 种(普通型女生):

  • Propagation.REQUIRED(须要有房子):有房子了咱们一起住,没房子了咱们一起赚钱买房子。
  • Propagation.SUPPORTS(能够有房子):有房子了就一起住,没房子了咱们就一起租房子。
  • Propagation.MANDATORY(强制有房子):有房子了就一起住,没房子了就离别。

不反对以后事务的“女生”也分为 3 种(强势型或者叫事业型):

  • Propagation.REQUIRES_NEW:不要你的房子,必须一起赚钱买房子。
  • Propagation.NOT_SUPPORTED:不要你的房子,必须一起租房子。
  • Propagation.NEVER:必须一起租房子,你有房子就离别。

最初一种是嵌套性事务 Propagation.NESTED,它属于懂事型女友,如果有房子了就以房子为根底做点小生意,卖个花生、水果啥的,如果买卖成了,那就持续倒退;如果失败了,至多还有房子;如果没房子也没关系,一起赚钱买房子。

事务流传机制应用与演示

接下来咱们演示一下事务流传机制的应用,以上面 3 个最典型的事务流传级别为例:

  • 反对以后事务的 REQUIRED;
  • 不反对以后事务的 REQUIRES_NEW;
  • 嵌套事务 NESTED。

下来咱们别离来看。

事务流传机制的示例,须要用到以下两张表:

-- 用户表
CREATE TABLE `user` (`id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `password` varchar(255) COLLATE utf8mb4_bin DEFAULT NULL,
  `createtime` datetime DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC;

-- 日志表
CREATE TABLE `log` (`id` int(11) NOT NULL AUTO_INCREMENT,
  `content` text NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin;

创立一个 Spring Boot 我的项目,外围业务代码有 3 个:UserController、UserServcie 以及 LogService。在 UserController 外面调用 UserService 增加用户,并调用 LogService 增加日志。

REQUIRED 应用演示

REQUIRED 反对以后事务。
UserController 实现代码如下,其中 save 办法开启了事务:

@RestController
public class UserController {
    @Resource
    private UserService userService;
    @Resource
    private LogService logService;

    @RequestMapping("/save")
    @Transactional
    public Object save(User user) {
        // 插入用户操作
        userService.save(user);
        // 插入日志
        logService.saveLog("用户插入:" + user.getName());
        return true;
    }
}

UserService 实现代码如下:

@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public int save(User user) {return userMapper.save(user);
    }
}

LogService 实现代码如下:

@Service
public class LogService {
    @Resource
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRED)
    public int saveLog(String content) {
        // 出现异常
        int i = 10 / 0;
        return logMapper.saveLog(content);
    }
}

执行后果:程序报错,两张表中都没有插入任何数据。

执行流程形容:

  1. 首先 UserService 中的增加用户办法失常执行实现。
  2. LogService 保留日志程序报错,因为应用的是 UserController 中的全局事务,所以整个事务回滚,步骤 1 中的操作也跟着回滚。
  3. 所以数据库中没有增加任何数据。

REQUIRED_NEW 应用演示

REQUIRED_NEW 不反对以后事务。
UserController 实现代码:

@RequestMapping("/save")
@Transactional
public Object save(User user) {
    // 插入用户操作
    userService.save(user);
    // 插入日志
    logService.saveLog("用户插入:" + user.getName());
    return true;
}

UserService 实现代码:

@Service
public class UserService {
    @Resource
    private UserMapper userMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int save(User user) {System.out.println("执行 save 办法.");
        return userMapper.save(user);
    }
}

LogService 实现代码:

@Service
public class LogService {
    @Resource
    private LogMapper logMapper;

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public int saveLog(String content) {
        // 出现异常
        int i = 10 / 0;
        return logMapper.saveLog(content);
    }
}

程序执行后果:

User 表中胜利增加了一条用户数据,Log 表执行失败,没有退出任何数据,但它并没有影响到 UserController 中的事务执行。

通过以上后果能够看出:LogService 中应用的是独自的事务,尽管 LogService 中的事务执行失败了,但并没有影响 UserController 和 UserService 中的事务。

NESTED 应用演示

NESTED 是嵌套事务。
UserController 实现代码如下:

@RequestMapping("/save")
@Transactional
public Object save(User user) {
    // 插入用户操作
    userService.save(user);
    return true;
}

UserService 实现代码如下:

@Transactional(propagation = Propagation.NESTED)
public int save(User user) {int result = userMapper.save(user);
    System.out.println("执行 save 办法.");
    // 插入日志
    logService.saveLog("用户插入:" + user.getName());
    return result;
}

LogService 实现代码如下:

@Transactional(propagation = Propagation.NESTED)
public int saveLog(String content) {
    // 出现异常
    int i = 10 / 0;
    return logMapper.saveLog(content);
}

最终执行后果,用户表和日志表都没有增加任何数据。

执行流程形容:

  1. UserController 中调用了 UserService 的增加用户办法,UserService 应用 NESTED 循环嵌套事务,并胜利执行了增加用户的办法。
  2. UserService 中调用了 LogService 的增加办法,LogService 应用了 NESTED 循环嵌套事务,但在办法执行中呈现的异样,因而回滚了以后事务。
  3. 因为 UserService 应用的是嵌套事务,所以产生回滚的事务是全局的,也就是说 UserService 中的增加用户办法也被回滚了,最终执行后果是用户表和日志表都没有增加任何数据。

总结

Spring 事务流传机制是蕴含多个事务的办法在互相调用时,事务是如何在这些办法间流传的。事务的流传级别有 7 个,反对以后事务的:REQUIRED、SUPPORTS、MANDATORY;不反对以后事务的:REQUIRES_NEW、NOT_SUPPORTED、NEVER,以及嵌套事务 NESTED,其中 REQUIRED 是默认的事务流传级别。

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

公众号:Java 面试真题解析

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

退出移动版