Spring嵌套事务回滚是怎么个事儿

前言

关于Spring的事物,我们都知道如果标明的事物的方法,该方法如果执行时抛出异常Exception,就会触发事物的rollback回滚机制,如果出现嵌套事物呢,事务回滚还能向我们所想那样进行回滚么,内外层事务的嵌套如果发生了异常,我们是否能去控制回滚策略呢,让我们一起来看看到底是怎么个事儿。

Spring事务原理

要想知道事务的回滚机制,首先要了解Spring事务的基本原理,我就直接进入主题,看TransactionAspectSupport的invokeWithinTransaction方法,因为我们知道Spring的事务是靠代理实现的,那么代理机制又离不开反射,大致流程如下,省略了非关键代码;

主要流程分为四步:判断是否需要创建事务、创建事务后执行业务方法、提交事务、异常处理

scss 复制代码
//事务属性
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);

//Spring事务的核心管理器
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);

final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

//判断是否需要创建一个事务
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
			// Standard transaction demarcation with getTransaction and commit/rollback calls.
			TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

			Object retVal;
			try {
				
				//创建事务后,调用具体的业务方法
				retVal = invocation.proceedWithInvocation();
			}
			catch (Throwable ex) {
				// 异常处理
				completeTransactionAfterThrowing(txInfo, ex);
				throw ex;
			}
			finally {
				cleanupTransactionInfo(txInfo);
			}
            //事务的提交
			commitTransactionAfterReturning(txInfo);
			return retVal;
		}

异常捕获

completeTransactionAfterThrowing这个方法主要是进行事务的异常捕获,

typescript 复制代码
//是否符合异常处理类型
public boolean rollbackOn(Throwable ex) {
	return (ex instanceof RuntimeException || ex instanceof Error);
}
//如果符合异常类型,则进行回滚
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
	try {
		txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
	}

事务回滚

AbstractPlatformTransactionManager这个抽象类的rollback方法

java 复制代码
//回滚已有的事务集
    @Override
	public final void rollback(TransactionStatus status) throws TransactionException {
		if (status.isCompleted()) {
			throw new IllegalTransactionStateException(
					"Transaction is already completed - do not call commit or rollback more than once per transaction");
		}

		DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
		processRollback(defStatus, false);
	}

processRollback回滚过程方法

⚠️这里有个savepoint概念,主要是一个事务的保存点,比如说一个事务执行begin DML... 执行后需要记录事务的保存点,之后可以回滚到某个保存点之前的状态。

scss 复制代码
     //是否基于嵌套事务创建了个savepoint
		if (status.hasSavepoint()) {
					if (status.isDebug()) {
						logger.debug("Rolling back transaction to savepoint");
					}
					status.rollbackToHeldSavepoint();
				}
				//是否是新事务
				else if (status.isNewTransaction()) {
					if (status.isDebug()) {
						logger.debug("Initiating transaction rollback");
					}
					doRollback(status);
				}
				//处理更大的事务
					if (status.hasTransaction()) {
						if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
							}
							doSetRollbackOnly(status);
						}
						else {
							if (status.isDebug()) {
								logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
							}
						}

status.isLocalRollbackOnly() 和isGlobalRollbackOnParticipationFailure() 这里是根据事务的传播特性来判断的,默认是REQUIRED;

  • status.isLocalRollbackOnly 默认是false
ini 复制代码
private boolean rollbackOnly = false;
  • isGlobalRollbackOnParticipationFailure默认是true 发生嵌套事务由外层事务统一决定
ini 复制代码
private boolean globalRollbackOnParticipationFailure = true;

嵌套事务场景

上面大致介绍了Spring事务的基本原理,下面让我们来看看具体的嵌套事务场景,事务是如何进行回滚的;

场景一

外层controller方法声明事务,方法里面有事务方法 A(insertUser),事务方法B(registerUser),我们将B方法进行 try catch 它的异常;

less 复制代码
@GetMapping("operationUser")
@Transactional(rollbackFor = Exception.class)
public void operationUser() throws Exception {

    SysUser sysUser = new SysUser();
    sysUser.setUserId(new Long(100));
    sysUser.setUserName("Lxlxxx");
    sysUser.setPhoneNumber("18888");

    //事务方法 A
    iSysUserService.insertUser(sysUser);
    try {
        //事务方法 B
        iSysUserService.registerUser(sysUser);
    } catch (Exception e) {
        e.printStackTrace();
    }

}
  • 暂定方法A
java 复制代码
@Transactional(rollbackFor = Exception.class)
public int insertUser(SysUser user)  {
    // 新增用户信息
    int rows = userMapper.insertUser(user);
    return rows;
}
  • 暂定方法B B方法传播特性定义默认Propagation.REQUIRED,手动抛出异常
java 复制代码
@Transactional(rollbackFor = Exception.class,propagation=Propagation.REQUIRED)
public boolean registerUser(SysUser user) throws Exception {
   //这个修改下UserId为了避免主键冲突
    user.setUserId(new Long(101));
    userMapper.insertUser(user);
    throw new Exception("注册用户失败");
}

结果 出现异常

内外层事务被统一回滚,因为外层的默认传播类型REQUIRED,嵌套的内层事务并没有开启一个新的事务,都处于统一一个大事务中,所以将所以事务都进行回滚;

场景二

java 复制代码
@GetMapping("operationUser")
@Transactional(propagation = Propagation.REQUIRED)
public void operationUser() throws Exception {

    SysUser sysUser = new SysUser();
    sysUser.setUserId(new Long(100));
    sysUser.setUserName("Lxlxxx");
    sysUser.setPhoneNumber("18888");

    //事务方法 A
    userMapper.insertUser(sysUser);

    try {
        //事务方法B
        SysRole sysRole =new SysRole();
        sysRole.setRoleId(new Long(2));
        sysRole.setRoleName("Lxlxxx");
        sysRole.setRoleKey("111");
        sysRole.setRoleSort(2);
        sysRole.setStatus("0");
        iSysUserService.registerUser(sysRole);
    } catch (Exception e) {
        e.printStackTrace();
    }

}

上面方法A不变,我们将方法B的propagation设置为NESTED,还是将B方法包裹在try/catch里面;

java 复制代码
@Transactional(propagation=Propagation.NESTED)
public boolean registerUser(SysRole role){
    roleMapper.insertRole(role);
    int i = 1/0;
    return true;
}

结果 sys_user表里新增了一条数据,也就是A方法的执行结果;

sys_role表里的新增数据被回滚,也就是B方法独立回滚了,并没有影响事务A的结果;

事务传播特性NESTED嵌套事务的------它能让事务部分回滚 ,B事务显然是一个嵌套事务,这里就涉及到savepoint ,嵌套事务B如果失败,将回滚到此savepoint

场景三

如果将异常方法在事务A里面,也就是方法的结尾会是如何呢?

结果 事务全部都进行回滚,毕竟都处于同一个外部事务;

场景四

事务方法中存在非事务方法,事务会失效么?回滚结果又是如何呢?

C方法中抛出异常

结果 外层事务没有失效,非事务方法中出现异常,A、B事务正常回滚,说明非事务方法出现异常,不会导致事务失效;

总结

事务的回滚以及嵌套事务的回滚,总体还是跟事务的传播特性密不可分,在我们写业务代码的时候通常都是离不开事务,如果不清楚事务回滚机制就会导致某些场景下出现脏数据,该回滚的没回滚掉,不该回滚的进行了回滚;

相关推荐
tg-zm8899961 小时前
2025返利商城源码/挂机自动收益可二开多语言/自定义返利比例/三级分销理财商城
java·mysql·php·laravel·1024程序员节
X***C8621 小时前
SpringBoot:几种常用的接口日期格式化方法
java·spring boot·后端
前端达人2 小时前
你的App消息推送为什么石沉大海?看Service Worker源码我终于懂了
java·开发语言
小光学长2 小时前
基于ssm的宠物交易系统的设计与实现850mb48h(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
java·前端·数据库
编程大师哥2 小时前
vxe-table 透视表分组汇总及排序基础配置
java
8***84822 小时前
spring security 超详细使用教程(接入springboot、前后端分离)
java·spring boot·spring
9***J6282 小时前
Spring Boot项目集成Redisson 原始依赖与 Spring Boot Starter 的流程
java·spring boot·后端
M***Z2102 小时前
SQL 建表语句详解
java·数据库·sql
v***7942 小时前
Spring Boot 热部署
java·spring boot·后端
执笔论英雄2 小时前
【RL】python协程
java·网络·人工智能·python·设计模式