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

总结

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

相关推荐
GJCTYU6 分钟前
spring中@Transactional注解和事务的实战理解附代码
数据库·spring boot·后端·spring·oracle·mybatis
艾迪的技术之路17 分钟前
redisson使用lock导致死锁问题
java·后端·面试
今天背单词了吗98035 分钟前
算法学习笔记:8.Bellman-Ford 算法——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·开发语言·后端·算法·最短路径问题
天天摸鱼的java工程师37 分钟前
使用 Spring Boot 整合高德地图实现路线规划功能
java·后端
东阳马生架构1 小时前
订单初版—2.生单链路中的技术问题说明文档
java
咖啡啡不加糖1 小时前
暴力破解漏洞与命令执行漏洞
java·后端·web安全
风象南1 小时前
SpringBoot敏感配置项加密与解密实战
java·spring boot·后端
DKPT1 小时前
Java享元模式实现方式与应用场景分析
java·笔记·学习·设计模式·享元模式
Percep_gan1 小时前
idea的使用小技巧,个人向
java·ide·intellij-idea