作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题
代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等
什么是 Spring 事务传播机制?
Spring 事务传播机制是指,包含多个事务的方法在相互调用时,事务是如何在这些方法间传播的。
例如:methodA事务方法调用methodB事务方法时,methodB是继承methodA的事务运行呢,还是为自己开启一个新事务运行,这就是由methodB 的事务传播行为决定的。
Spring 的事务传播机制
Spring 事务传播机制使用 @Transactional(propagation=Propagation.REQUIRED) 来定义,Spring 一共定义了 7 中传播行为:
REQUIRED:默认的事务传播级别。如果当前没有事务,Spring 会创建一个新的事务;如果已经存在事务,则加入该事务。这意味着被调用的方法会在调用者的事务上下文中运行。SUPPORTS:如果当前存在事务,方法会在这个事务中运行;如果没有事务,则方法在非事务的环境中执行。MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,它会抛出异常。这种传播行为要求调用方法必须在一个现有的事务中运行。REQUIRES_NEW:总会创建一个新的事务,如果当前存在事务,则将当前事务挂起。也就是说不管外部方法是否开启事务,REQUIRES_NEW修饰的方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。NEVER:以非事务方式运行,如果当前存在事务,它会抛出异常。这种传播行为确保方法永远不在事务环境中运行。NESTED:如果当前存在事务,则在嵌套事务内运行。如果当前事务回滚,嵌套事务也会回滚,但嵌套事务的回滚不会导致当前事务回滚。如果当前没有事务,则其行为像REQUIRED。
七种传播行为,可以分为三大类,如下图:

现在我们来详细看看这七种传播行为。为了下面的演示,我们需要新建两张表 :
create table user(
`id` int(11) NOT NULL AUTO_INCREMENT comment 'id',
name varchar(100) not null comment '用户名',
PRIMARY KEY (id)
)ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC comment '用户信息表';
create table blog(
`id` int(11) NOT NULL AUTO_INCREMENT comment 'id',
title varchar(100) not null comment '标题',
PRIMARY KEY (id)
)ENGINE=InnoDB AUTO_INCREMENT=10000 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_bin ROW_FORMAT=DYNAMIC comment '博客信息表';
表够简单吧。^_^....
利用如下代码来验证:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private BlogService blogService;
// 这里添加不同的事务传播行为
@Transactional(propagation = Propagation.XXXX)
public void addUser() {
User user = new User();
user.setName("码哥");
userMapper.insertSelective(user);
blogService.addBlog();
}
}
@Service
public class BlogService {
@Autowired
private BlogMapper blogMapper;
// 这里添加不同的事务传播行为
@Transactional(propagation = Propagation.XXXX)
public void addBlog() {
Blog blog = new Blog();
blog.setTitle("Java 面试 600 讲");
blogMapper.insertSelective(blog);
}
}
由于需要观察 Spring 管理事务的日志,需要将 Spring 日志相关的级别调整为 DEBUG,如下:
<Logger name="org.springframework.transaction.interceptor" level="DEBUG"/>
码哥分析前面 5 种,NEVER 和 NESTED 就不分析了,使用场景没有相对会少些。
REQUIRED
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
例如 :
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 调用 B
methodB();
}
@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
}
如果我们单独执行 methodB(),由于当前上下文不存在事务,则会开启一个新的事务。如果我们调用 methodA(),由于当前上下文不存在事务,所以会开启一个新的事物,再调用 methodB(),methodB() 发现当前上下文已经存在了事务,则会加入到 methodA() 的事务中。
我们将上面的示例代码的传播行为调整为 REQUIRED,执行代码,打印如下日志:

- 1、表示创建了一个事务,传播行为为
PROPAGATION_REQUIRED,事务隔离级别为ISOLATION_DEFAULT。addUser()开启事务。 - 2、表示加入已经存在的事务。
addUser()创建了事务,然后调用addBlog(),addBlog()发现当前已经有事务了 ,直接加入即可 - 3、提交事务
如果在方法执行过程中发生了异常,并且这个异常被标记为触发回滚,整个事务都会回滚。比如我们在 addBlog() 方法中加入 int i = 1 / 10;,如下:
@Transactional(propagation = Propagation.REQUIRED)
public void addBlog() {
Blog blog = new Blog();
blog.setTitle("Java 面试 600 讲");
int i = 1 / 0;
blogMapper.insertSelective(blog);
}
执行,日志如下:

addUser() 在正常提交,但是 addBlog() 抛出 ArithmeticException 异常,导致整个事务回滚。
我们知道异常分为运行时异常(RuntimeException)、检查型异常(Exception)和错误(Error),如果我们不使用 rollbackFor 或者 noRollbackFor 来定义事务回滚行为,那么 Spring 事务只会在遇到运行时异常(RuntimeException)和错误(Error)时触发回滚。我们将 addBlog() 调整为这样:
@Transactional(propagation = Propagation.REQUIRED)
public void addBlog() throws Exception {
Blog blog = new Blog();
blog.setTitle("Java 面试 600 讲");
blogMapper.insertSelective(blog);
throw new Exception("抛个异常玩玩..");
}
执行,日志如下:

抛出 Exception ,事务依然正常提交。
SUPPORTS
如果当前存在事务,方法会在这个事务中运行;如果没有事务,则方法在非事务的环境中执行。
例如:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 调用 B
methodB();
}
@Transactional(propagation = Propagation.SUPPORTS)
public void methodB() {
}
如果我们单纯调用 methodB() ,则它会以非事务的方式运行。如果我们调用 methodA(),由于 methodA() 有事务,所以 methodB() 会加入到 methodA() 的使用中运行。
下面我们实战演示下。将 addBlog() 的事务传播行为调整为 SUPPORTS:
@Transactional(propagation = Propagation.SUPPORTS)
public void addBlog() throws Exception {
//...
}
我们单独调用下 addBlog(),日志如下:

没有任务事务相关的日志。
我们在通过 addUser() 来调用 addBlog(),日志如下:

对于事务回滚,如果没有事务的话,及时出现了异常也不会回滚。但是如果有事务的话,则回滚由异常类型和 rollbackFor 配置相关。
MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,它会抛出异常。他要求调用方必须在一个现有的事务中运行。
例如:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 调用 B
methodB();
}
@Transactional(propagation = Propagation.MANDATORY)
public void methodB() {
}
如果我们单独调用 methodB() ,则会抛出 IllegalTransactionStateException 异常。如果通过 methodA() 调用 methodB(),由于 methodA() 存在事务中,所以 methodB() 会加入当前事务中。
我们来实例演示下,将 addBlog() 的事务传播行为调整为 MANDATORY。
@Transactional(propagation = Propagation.MANDATORY)
public void addBlog() throws Exception {
//...
}
先单独调用 addBlog(),运行日志如下:

调用方法没有事务,则抛出 IllegalTransactionStateException 异常。
我们在通过 addUser() 来调用,运行日志如下:

addUser() 有事务,addBlog() 加入到 addUser() 中的事务来。
REQUIRES_NEW
总会创建一个新的事务,如果当前存在事务,则将当前事务挂起。也就是说不管外部方法是否开启事务,
REQUIRES_NEW修饰的方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。
例如:
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
// 调用 B
methodB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
}
无论你怎么调用 methodB(),它都会新开一个自己的事务。
下面代码演示下。我们将 addBlog() 的事务传播行为调整为 REQUIRES_NEW:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void addBlog() throws Exception {
// ...
}
单独调用 addBlog(),日志如下:

单独调用它会创建一个新的事务,传播行为为 REQUIRES_NEW。
如果通过 addUser() 去调用呢?日志如下:

- 1、
addUser()事务传播行为为REQUIRED,调用它时由于当前没有事务,所以它会另开一个事务。 - 2、
addUser()调用addBlog(),尽管addBlog()当前处于事务当中,但是由于它的的事务传播行为是REQUIRES_NEW,所以它会重新开启一个新的事务Suspending current transaction:表示挂起当前事务 ,即将 addUser() 的事务暂停。creating new transaction with name:表示创建一个新的事务。
- 3、
addBlog()提交事务 - 4、
addUser()提交事务Resuming suspended transaction after completion of inner transaction:恢复暂停的事务。
上面提到 REQUIRES_NEW 与当前事务是相互独立,互不干扰的。我们让 addBlog() 抛个异常看看 :

你会发现两个事务都回滚了,什么原因?因为 addBlog() 抛了异常后,addUser() 对这个异常没有hold 住,也在抛异常,事务肯定会回滚。那如果 addUser() 抛出异常呢?日志如下:

addBlog() 依然提交了事务,但是 addUser() 回滚了事务。
NOT_SUPPORTED
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
直接演示吧。将 addBlog() 事务传播行为调整为 NOT_SUPPORTED,单独调用 addBlog() ,日志如下:

单独调用,没有事务。如果通过 addUser() 调用呢 ?

Spring事务传播机制详解
628

被折叠的 条评论
为什么被折叠?



