今天测试环境同事写测试代码的时候,出现了一个Transaction rolled back because it has been marked as rollback-only,还发了组内的告警邮件
过多的信息可不敢截取啊,展示其中的关键信息就好。
辣么问题来了,这个问题是肿么肥四呢?
稍加思考,立马恍然大悟!
为了重现这个异常,我搭建了一个小小的demo:
@Service
public class TestService {
@Autowired
private StudentMapper studentMapper;
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test(){
studentMapper.get(1L);
throw new RuntimeException();
}
}
@Service
public class StudentService {
@Autowired
private StudentMapper studentMapper;
@Autowired private TestService testService;
@Transactional(propagation = Propagation.REQUIRED, readOnly = true, noRollbackFor = Exception.class)
public Student get(Long id) {
Student student = studentMapper.get(id);
testService.test();
return student;
}
}
@RequestMapping("/student")
@RestController
public class StudentController {
@Autowired
private StudentService studentService;
@RequestMapping("one")
public Student save(@RequestParam("id") Long id) {
return studentService.get(id);
}
}
请求一下,果然不出山人所料
让我们来分析一波
首先 test()方法上面加的是一个正常的读写事务,propagation = Propagation.REQUIRED
其次StudentService.get()加的是一个只读的事务,propagation = Propagation.REQUIRED,并且调用了 test()
辣么,推理可得test()与StudentService.get()是公用的同一个事务,然后test()方法内抛了一个异常,所以test()执行完之后,事务会被标记为回滚
但是!
StudentService.get()说我是个只读的啊,我不想要回滚,谁也不能勉强我,所以我要正常提交事务
这样问题就来了啊,事务已经被标记为回滚了,这个时候要去提交,Spring说这我怎么办啊,很蓝的啦,只能抛一个异常这样子的啦;
最终就是我们看到的 Transaction rolled back because it has been marked as rollback-only
这个问题的本质是什么:是serviceA调用了serviceB,两者在同一个事务,B要回滚A要commit,就会出现这样的问题;
所以StudentService.get()换一种写法也可以实现同样的效果
@Transactional(propagation = Propagation.REQUIRED)
public Student get(Long id) {
Student student = studentMapper.get(id);
try{
testService.test();
}catch (Exception exception){ }
return student;
}
源码分析:
通过方法断点,我们可以很快地定位到,事务执行的切面代码,关键代码展示如下
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass, final InvocationCallback invocation) throws Throwable {
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
//获取事务管理器
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
//切点定义解析,也就是目标方法,如test()与StudentService.get()
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 创建事务
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification); Object retVal = null;
try {
//执行目标方法
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
// 目标方法异常后的事务处理方式
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
cleanupTransactionInfo(txInfo);
}
// 目标方法正常执行后的事务处理方式
commitTransactionAfterReturning(txInfo); return retVal;
}
}
先看事务的创建逻辑
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm, @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
// TransactionAttribute命名,不重要
if (txAttr != null && txAttr.getName() == null) {
txAttr = new DelegatingTransactionAttribute(txAttr) {
@Override
public String getName() {
return joinpointIdentification;
}
};
}
TransactionStatus status = null;
if (txAttr != null) {
if (tm != null) {
//获取事务
status = tm.getTransaction(txAttr);
}
}
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
获取事务的关键代码:
@Override public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
Object transaction = doGetTransaction();
if (isExistingTransaction(transaction)) {
// 如果当前已经存在一个事务的处理方式
return handleExistingTransaction(definition, transaction, debugEnabled);
}
//如果没有事务,并且PROPAGATION是下列几种PROPAGATION_REQUIRED PROPAGATION_REQUIRES_NEW PROPAGATION_NESTED
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW || definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 初始化事务的状态
DefaultTransactionStatus status = newTransactionStatus( definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
// 开启事务
doBegin(transaction, definition);
// 不知道,暂时不管
prepareSynchronization(status, definition); return status;
} catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
} else {
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(definition, null, true, newSynchronization, debugEnabled, null);
}
}
通过上面代码可以看到,StudentService.get()方法执行的时候,当前是没有事务的,所以执行代码是 初始化事务状态——开启事务等操作;对于test()方法执行的时候已经有一个事务了,所以需要执行handleExistingTransaction()方法,主要逻辑如下:
private TransactionStatus handleExistingTransaction( TransactionDefinition definition, Object transaction, boolean debugEnabled) throws TransactionException {
...
if (isValidateExistingTransaction()) {
// 隔离级别是否一致
if (definition.getIsolationLevel() != TransactionDefinition.ISOLATION_DEFAULT) {
Integer currentIsolationLevel = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel();
if (currentIsolationLevel == null || currentIsolationLevel != definition.getIsolationLevel()) {
Constants isoConstants = DefaultTransactionDefinition.constants;
throw new IllegalTransactionStateException("Participating transaction with definition [" + definition + "] specifies isolation level which is incompatible with existing transaction: " + (currentIsolationLevel != null ? isoConstants.toCode(currentIsolationLevel, DefaultTransactionDefinition.PREFIX_ISOLATION) : "(unknown)"));
}
}
if (!definition.isReadOnly()) {
if (TransactionSynchronizationManager.isCurrentTransactionReadOnly()) {
throw new IllegalTransactionStateException("Participating transaction with definition [" + definition + "] is not marked as read-only but existing transaction is");
}
}
}
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
// 复用老事务的连接
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
经过上述代码可以看到,当事务嵌套的时候,如果被嵌套的事务propagation = Propagation.REQUIRED,那么就会复用外层的事务,也就是我们说的公用了同一个事务连接;
那么当test()发生异常的时候执行completeTransactionAfterThrowing()逻辑,主要代码如下
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
// 如果发生的异常,在回滚的异常范围内,那么走事务回滚逻辑
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
// 标记事务状态为回滚/回滚事务
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
} catch (TransactionSystemException ex2) {
throw ex2;
} catch (RuntimeException | Error ex2) {
throw ex2;
}
} else {
...
}
}
}
其中对于txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus()); 最终会判断如果当前事务方法是被嵌套的事务,并且共用了一个大事务,那么会把这个事务标记为rollbackOnly,代码为
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
而对于StudentService.get()方法(以try-catch方式为例),异常被捕获,整个方法正常执行,所以会进入到commitTransactionAfterReturning(txInfo);
最终追踪到
@Override public final void commit(TransactionStatus status) throws TransactionException {
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
if (defStatus.isLocalRollbackOnly()) {
processRollback(defStatus, false);
return;
}
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
//如果发现事务被标记为 rollbackOnly了,那么不会提交事务,反而执行回滚逻辑,并且,标记这个回滚是超出预期的
processRollback(defStatus, true);
return;
}
//提交事务
processCommit(defStatus);
}
最终对于超出预期的事务回滚,在回滚逻辑执行完成后,抛出异常
if (unexpectedRollback) {
throw new UnexpectedRollbackException( "Transaction rolled back because it has been marked as rollback-only");
}
以上就是整个代码追踪的全部过程啦