在spring中管理事务通常用的便是@Transactional注解,它是利用aop原理,拦截被@Transactional修饰的方法(切点),生成一个代理类执行原方法,并且在执行前后开启和提交事务:
//开启事务
TransactionAspectSupport.TransactionInfo txInfo = this.createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
try {
//执行原方法
retVal = invocation.proceedWithInvocation();
} catch (Throwable var18) {
//异常处理,回滚
this.completeTransactionAfterThrowing(txInfo, var18);
throw var18;
} finally {
this.cleanupTransactionInfo(txInfo);
}
//提交事务
this.commitTransactionAfterReturning(txInfo);
spring事务有七种传播级别,其中最常用的是:
PROPAGATION_REQUIRED:Spring的默认传播级别,如果上下文中存在事务则加入当前事务,如果不存在事务则新建事务执行。
PROPAGATION_REQUIRES_NEW:该传播级别每次执行都会创建新事务,并同时将上下文中的事务挂起,执行完当前线程后再恢复上下文中事务。(子事务的执行结果不影响父事务的执行和回滚)。
PROPAGATION_REQUIRED
情景一
@Transactional
public void test1(User user) {
userMapper.insert(user);
required();
}
@Transactional
public void required() {
throw new NullPointerException;
}
执行test()方法,抛出异常,会回滚事务。但是如果将required()方法的异常进行catch,改成:
情景二
@Transactional
public void test1(User user) {
userMapper.insert(user);
try{
required();
catch(Exception e){
e.printStackTrace();
}
}
@Transactional
public void required() {
throw new NullPointerException;
}
结果又如何?再如果将required()方法的事务传播改为PROPAGATION_REQUIRES_NEW呢?
情景三
@Transactional
public void test1(User user) {
userMapper.insert(user);
try{
required();
catch(Exception e){
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void required() {
throw new NullPointerException;
}
如果将上述try-catch去掉,又会提交成功吗?
情景四
@Transactional
public void test1(User user) {
userMapper.insert(user);
required();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void required() {
throw new NullPointerException;
}
讨论上述四种情况前,先看一下源码中对抛出异常的处理,层层追溯(中间非核心代码不贴了)到processRallback()方法:
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
this.triggerBeforeCompletion(status);
if (status.hasSavepoint()) {
if (status.isDebug()) {
this.logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
//代码1 注意这里,如果当前事务状态是新的事务
} else if (status.isNewTransaction()) {
if (status.isDebug()) {
this.logger.debug("Initiating transaction rollback");
}
//代码2 回滚当前事务
this.doRollback(status);
} else {
//代码3 注意这里。如果当前已经存在事务
if (status.hasTransaction()) {
if (!status.isLocalRollbackOnly() && !this.isGlobalRollbackOnParticipationFailure()) {
if (status.isDebug()) {
this.logger.debug("Participating transaction failed - letting transaction originator decide on rollback");
}
} else {
if (status.isDebug()) {
this.logger.debug("Participating transaction failed - marking existing transaction as rollback-only");
}
//代码4 只允许回滚
this.doSetRollbackOnly(status);
}
} else {
this.logger.debug("Should roll back transaction but cannot - no transaction available");
}
if (!this.isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
} catch (Error | RuntimeException var8) {
this.triggerAfterCompletion(status, 2);
throw var8;
}
this.triggerAfterCompletion(status, 1);
if (unexpectedRollback) {
throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");
}
} finally {
this.cleanupAfterCompletion(status);
}
}
上述代码1处,isNewTransaction()方法:
public boolean isNewTransaction() {
return this.hasTransaction() && this.newTransaction;
}
也就是说,当前已存在事务,且是一个新的事务才返回true。新的事务就是指未加入到其他事务中的事务。
情景一的代码自然不用说,默认传播级别抛异常未捕获一定会回滚。
情景二的代码,将抛异常的部分捕获,而内层方法又是默认传播级别,也就是两个事务会合二为一,并没有新的事务,所以执行到processRallback()方法时,会走代码3的分支,从而执行代码4,设置当前事务只允许回滚。而情景二的代码中,外层方法中异常已被捕获,会正常执行到提交事务commitTransactionAfterReturning(txInfo)方法这一步,而又只允许回滚,所以会抛出异常:
org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
情景三的代码将内层方法的传播级别改为Propagation.REQUIRES_NEW,会创建一个新的事务,与外层方法的事务没有关系,内层方法执行到processRallback()方法时,已经有一个事务,并且是一个新的事务,所有会走代码1的分支,从而执行代码2进行回滚。因为是独立的事务,所以此时回滚的仅仅是内层方法的事务,外层方法因为异常被捕获,所以会正常提交。
情景四的代码其实很简单,抛出异常未被捕获,无论外层方法还是内层方法,都会回滚,不会提交。
PROPAGATION_REQUIRED和PROPAGATION_REQUIRES_NEW两种传播级别已经可以解决95%的场景,@Transactional注解要灵活运用。