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

总结

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

相关推荐
IT学长编程1 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇1 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
杨哥带你写代码1 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端
郭二哈2 小时前
C++——模板进阶、继承
java·服务器·c++
A尘埃2 小时前
SpringBoot的数据访问
java·spring boot·后端
yang-23072 小时前
端口冲突的解决方案以及SpringBoot自动检测可用端口demo
java·spring boot·后端
沉登c2 小时前
幂等性接口实现
java·rpc
代码之光_19802 小时前
SpringBoot校园资料分享平台:设计与实现
java·spring boot·后端
科技资讯早知道3 小时前
java计算机毕设课设—坦克大战游戏
java·开发语言·游戏·毕业设计·课程设计·毕设
小比卡丘4 小时前
C语言进阶版第17课—自定义类型:联合和枚举
android·java·c语言