Spring七大事务传递机制深入分析实现原理
作者:一路向阳向北 发布时间:2022-12-21 16:28:37
Spring事务传递机制原理
首先,我们通过org.springframework.transaction.annotation.Propagation来了解一下spring事务的传播定义:
1. REQUIRED(默认):
Support a current transaction, create a new one if none exists.
支持当前事务,如果没有则创建一个新的
2. SUPPORTS
Support a current transaction, execute non-transactionally if none exists.
支持当前事务,如果没有则不使用事务
3. MANDATORY
Support a current transaction, throw an exception if none exists
支持当前事务,如果没有事务则报错
4. REQUIRED_NEW
Create a new transaction, and suspend the current transaction if one exists.
新建一个事务,同时将当前事务挂起
5. NOT_SUPPORTED
Execute non-transactionally, suspend the current transaction if one exists
以无事务的方式执行,如果当前有事务则将其挂起
6. NEVER
Execute non-transactionally, throw an exception if a transaction exists.
以无事务的方式执行,如果当前有事务则报错
7. NESTED
Execute within a nested transaction if a current transaction exists,behave like PROPAGATION_REQUIRED else
如果当前有事务,则在当前事务内部嵌套一个事务,内部事务的回滚不影响当前事务。如果当前没有事务,就相当于REQUIRED
Note: Actual creation of a nested transaction will only work on specific transaction managers. Out of the box, this only applies to
the JDBC DataSourceTransactionManager when working on a JDBC 3.0 driver.
Some JTA providers might support nested transactions as well.
注意:该定义只能在JDBC3.0驱动下的DataSourceTransactionManager事务管理器中使用,有些JTA事务可能也会支持
接下来我们通过代码验证一下spring事务的传递性,在UserServiceImpl类添加两个方法如下:
@Transactional(propagation = Propagation.NEVER)
public User findById(Long id) {
User user = userMapper.findById(id);
System.out.println("find user:"+user);
return user;
}
@Transactional
public void transactionTest(int t) {
findById(t+0L);
}
我们调用transactionTest方法,transactionTest没有配置Propagation,所以默认是REQUIRED,会在当前新建一个事务。transactionTest内部调用findById,由于findById事务传播定义为NEVER,表明它当前不能有事务,按理说这里会抛出异常,但是我们利用junit执行后发现,transactionTest是可以正常执行的。
事实上,如果使用@Transaction方法里嵌套调用的是同一个类的方法,spring代理会忽略嵌套方法的@Transaction配置。但是,如果是其他注入对象的方法,那么@Transaction配置就会生效。我们将上面的transactionTest方法的事务传播定义为NERVER,并新增一个insert操作,即使insert启用了事务并且抛出异常,但是事务不会生效,也不会有回滚的说法,程序会抛出异常但是数据会保存到数据库中:
@Transactional(propagation = Propagation.NEVER)
public void transactionTest(int t) {
findById(t+0L);
insertUser("huangxl","abc123");
}
@Transactional
public int insertUser(String name, String password) {
User user = new User();
user.setPassword(password);
user.setUsername(name);
int insertCount = userMapper.insertEntity(user);
if(insertCount == 1 ){
throw new RuntimeException("test transaction roll back");
}
return insertCount;
}
接下来我们来测试不同类之间的方法(事务)调用,以下的测试都是基于junit执行TransactionTestServiceImpl.test()方法
一、Propagation.NERVER的测试
下面我们将UserService注入到TransactionTestServiceImpl中,test方法使用@Transactional,UserService findById事务传播定义不变,还是NERVER。
UserserviceImpl:
@Service
public class TransactionTestServiceImpl implements TransactionTestService {
@Autowired
private UserService userService;
@Override
@Transactional
public void test() {
userService.findById(1L);
}
}
TransactionTestServiceImpl:
@Service
public class UserServiceImpl implements UserService {
@Override
@Transactional(propagation = Propagation.NEVER)
public User findById(Long id) {
User user = userMapper.findById(id);
System.out.println("find user:"+user);
return user;
}
}
由于test默认启用了事务,findById不允许当前有事务,所以我们执行test方法后会发现程序抛出了异常:
org.springframework.transaction.IllegalTransactionStateException: Existing transaction found for transaction marked with propagation ‘never’
结论:
NERVER 不允许当前存在事务
二、Propagation.REQUIRED的测试
UserserviceImpl:
@Transactional
public int insertUser(String name, String password) {
User user = new User();
user.setPassword(password);
user.setUsername(name);
int insertCount = userMapper.insertEntity(user);
if(insertCount == 1 ){
throw new RuntimeException("test transaction roll back");
}
return insertCount;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
try {
userService.insertUser("abc", "123");
} catch (Exception e) {
//do Nothing
}
userMapper.updateUserPassWord(1L, "456");
}
我们会发现,即使捕获了userService.insertUser抛出的异常,test还是把insertUser和updateUserPassword操作当成是一个整体,整个事务还是回滚了,程序抛出了下面的异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
结论:
REQUIRED子事务会影响当前事务的提交、回滚
三、Propagation.NESTED的测试
UserserviceImpl:
@Transactional(propagation = Propagation.NESTED)
public int insertUser(String name, String password) {
User user = new User();
user.setPassword(password);
user.setUsername(name);
int insertCount = userMapper.insertEntity(user);
if(insertCount == 1 ){
throw new RuntimeException("test transaction roll back");
}
return insertCount;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
try {
userService.insertUser("abc", "123");
} catch (Exception e) {
//do Nothing
}
userMapper.updateUserPassWord(1L, "456");
}
程序正常运行,因为NESTED内部事务回滚不影响外部事务。假如这个时候我们把test的@Transactional去掉再运行test方法,发现insertUser没有插入用户信息,说明当前没有事务的情况下,NESTED会默认创建一个事务,类似于REQUIRED。
如果我们把程序改为下面的情况:
UserserviceImpl:
@Transactional(propagation = Propagation.NESTED)
public int insertUser(String name, String password) {
User user = new User();
user.setPassword(password);
user.setUsername(name);
int insertCount = userMapper.insertEntity(user);
return insertCount;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
userService.insertUser("abc", "123");
int updateRow = userMapper.updateUserPassWord(1L, "456");
if (updateRow == 1) {
throw new RuntimeException("transational roll back");
}
}
我们会发现没有插入用户信息,当前事务和子事务全部回滚。
结论:
NESTED子事务回滚不会影响当前事务的提交(catch回滚异常的情况下),但是当前事务回滚会回滚子事务。也就是说只有当前事务提交成功了,子事务才会提交成功。
四、Propagation.REQUIRED_NEW的测试
UserserviceImpl:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int insertUser(String name, String password) {
User user = new User();
user.setPassword(password);
user.setUsername(name);
int insertCount = userMapper.insertEntity(user);
return insertCount;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
userService.insertUser("abc", "123");
int updateRow = userMapper.updateUserPassWord(1L, "456");
if (updateRow == 1) {
throw new RuntimeException("transational roll back");
}
}
运行结果:程序报错,但是有用户信息插入。
将程序改为下面的样子:
UserserviceImpl:
@Transactional(propagation = Propagation.REQUIRES_NEW)
public int updateUserPassWorld(Long id, String password) {
int update = userMapper.updateUserPassWord(id,password);
return update;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
//当前事务
userMapper.updateUserPassWord(28L, "123456");
//执行REQUIRES_NEW事务
userService.updateUserPassWorld(28L, "000000");
System.out.println("commit");
}
执行程序,发现程序迟迟没有打印字符串commit,发生了死锁。
结论:
REQUIRES_NEW会启用一个新的事务,事务拥有完全独立的能力,它不依赖于当前事务,执行时会挂起当前事务,直到REQUIRES_NEW事务完成提交后才会提交当前事务,如果当前事务与REQUIRES_NEW 存在锁竞争,会导致死锁。
五、NOT_SUPPORTED的测试
UserserviceImpl:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int updateUserPassWorld(Long id, String password) {
int updateRow = userMapper.updateUserPassWord(id,password);
if(updateRow ==1 ){
throw new RuntimeException("roll back test");
}
return updateRow;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
userService.updateUserPassWorld(28L, "000000");
}
程序运行报错,但是id为28的用户密码还是更新了。
将程序改为下面这个情况:
UserserviceImpl:
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public int updateUserPassWorld(Long id, String password) {
int update = userMapper.updateUserPassWord(id,password);
return update;
}
TransactionTestServiceImpl:
@Transactional
public void test() {
//当前事务
userMapper.updateUserPassWord(28L, "123456");
//执行REQUIRES_NEW事务
userService.updateUserPassWorld(28L, "000000");
System.out.println("commit");
}
执行程序,发现程序迟迟没有打印字符串commit,发生了死锁。
结论:
NOT_SUPPORTED会挂起当前事务,并且NOT_SUPPORTED定义的方法内部不启用显示事务,如果NOT_SUPPORTED和当前事务存在锁竞争,会发生死锁。
六、NOT_SUPPORTED的测试
UserserviceImpl:
@Transactional(propagation = Propagation.MANDATORY)
public int updateUserPassWorld(Long id, String password) {
int updateRow = userMapper.updateUserPassWord(id,password);
return updateRow;
}
TransactionTestServiceImpl:
public void test() {
userService.updateUserPassWorld(28L, "123456");
}
程序运行错误:
org.springframework.transaction.IllegalTransactionStateException: No existing transaction found for transaction marked with propagation ‘mandatory’
结论:
MANDATORY必须包含在事务中,如果事务不存在,则抛出异常
来源:https://blog.csdn.net/weixin_36665875/article/details/129759753


猜你喜欢
- 这篇文章主要介绍了SpringBoot项目没有把依赖的jar包一起打包的问题解决,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一
- 一、下载安装包 1. JDK1.8百度云下载路径:百度网盘下载链接: https://pan.baidu
- 目录前言步入正题类的加载过程:1.加载2.验证3.准备4.解析5.初始化类加载器源码总结前言学生时代应抱着问题去学习一门语言,例如:在学习j
- 为什么要学习Android与H5互调?微信,QQ空间等大量软件都内嵌了H5,不得不说是一种趋势。Android与H5互调可以让我们的实现混合
- 前言我们很多小伙伴平时都是做JAVA开发的,那么作为一名合格的工程师,你是否有仔细的思考过JVM的运行原理呢。如果懂得了JVM的运行原理和内
- 众所周知,当你点击一个超链接进行跳转时,WebView会自动将当前地址作为Referer(引荐)发给服务器,因此很多服务器端程序通过是否包含
- 本文实例讲述了Android开发中应用程序分享功能。分享给大家供大家参考,具体如下:Intent shareIntent = new Int
- 一、坐标分类地图坐标大致分为几种: 1、GPS、WGS84,也就是原始坐标体系,
- 一、前言本文主要是从官方文档中筛选出一些常见的适配项,若有任何纰漏或需要补充的,欢迎大家在评论区指出。二、版本适配1. 限制 HTTP 网络
- java中对数组进行排序使用Array.sort() 这个默认是升序@Test public void index4(){ &n
- 在java里, 我们可以使用Executors.newFixedThreadPool 来创建线程池, 然后就可以不停的创建新任务,并用线程池
- 本文初步探讨了C#缓存的原理及应用,并以实例加以分析总结,这些对C#初学者来说是很有必要熟练掌握的内容。具体如下:一、概述:缓存应用目的:缓
- 之前的项目中,在Socket通信的时候需要传int类型的值,不过java中outputsteam貌似不能直接传int类型,只能传byte[]
- 1.简单计算器使用AWT编程,FrameNORTH区域放置TextField组件,将指定为4行5列GridLayout布局管理器的Panel
- 一、链表的概念和结构1.1 链表的概念简单来说链表是物理上不一定连续,但是逻辑上一定连续的一种数据结构1.2 链表的分类实际中链表的结构非常
- 这篇文章主要介绍了Java模拟多线程实现抢票,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考
- jQuery的方法连缀使用起来非常方便,可以简化语句,让代码变得清晰简洁。那C#的类方法能不能也实现类似的功能呢?基于这样的疑惑,研究了一下
- SpringBoot启动自动终止也不报错Error starting ApplicationContext. To display the
- 使用YZMHelper帮助类即可using System;using System.Web;using System.Drawing;usi
- 题目:编写一个程序,在面板上移动小球。应该定义一个面板类来显示小球,并提供向上下左右移动小球的方法。请进行边界检查以防止小球移动到视线之外。