前言
关于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事务正常回滚,说明非事务方法出现异常,不会导致事务失效;
总结
事务的回滚以及嵌套事务的回滚,总体还是跟事务的传播特性密不可分,在我们写业务代码的时候通常都是离不开事务,如果不清楚事务回滚机制就会导致某些场景下出现脏数据,该回滚的没回滚掉,不该回滚的进行了回滚;