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事务正常回滚,说明非事务方法出现异常,不会导致事务失效;

总结

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

相关推荐
李慕婉学姐9 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
奋进的芋圆11 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin11 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model200511 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉11 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国11 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_9418824812 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
華勳全栈12 小时前
两天开发完成智能体平台
java·spring·go
alonewolf_9912 小时前
Spring MVC重点功能底层源码深度解析
java·spring·mvc
沛沛老爹12 小时前
Java泛型擦除:原理、实践与应对策略
java·开发语言·人工智能·企业开发·发展趋势·技术原理