上一篇文章已经详细分析了spring中如何创建事务(spring源码分析之事务transaction上篇),今天这篇文章主要是介绍spring中事务的回滚、事务提交、以及使用事务时的注意事项。这篇文章与上一篇文章有强关联,建议先去看上篇
一、事务回滚
我们只分析常用的传播属性REQUIRED(默认)、REQUIRES_NEW、NESTED,其他的可以自行阅读,也比较简单。我们还是拿上篇文章的例子来分析事务回滚和事务提交
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED)
public void testInsert() throws Exception {
String sql = "insert into user (name,age,sex) values(?,?,?)";
jdbcTemplate.update(sql,new Object[]{
"taott11",12,"man"});
//int a = 1/0;
//throw new Exception();
String sql2 = "insert into user (name,age,sex) values(?,?,?)";
jdbcTemplate.update(sql2,new Object[]{
"tao",18,"woman"});
//这里又调用了另外一个有事务标记的方法(动态代理方法)
productService.queryUesr();
}
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void queryUesr() throws Exception {
query();
throw new RuntimeException();
}
我们暂时先不要看方法上面的传播属性,因为我们要针对各种情况做详细的分析,我们暂时具体分析下面四种情况
1.第一个业务方法抛异常即testInsert方法
2.第二个传播属性为REQUIRED,第二个业务方法抛异常即queryUesr
3.第二个传播属性为REQUIRES_NEW,第二个业务方法抛异常即queryUesr
4.第二个传播属性为NESTED,第二个业务方法抛异常即queryUesr
第一个方法是UserService对象,第二个方法是ProductService对象
TransactionAspectSupport.invokeWithinTransaction()
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
//省略...创建事务的代码之前分析过,不再分析
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
//1.调用真是的业务逻辑
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
//2.核心逻辑之一,事务回滚
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
if (vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
//3.核心逻辑之一,事务提交
commitTransactionAfterReturning(txInfo);
return retVal;
}
//...省略代码,暂时先不看
}
1.第一个方法抛异常分析
第一个方法抛异常–即注释1的地方抛异常,假如我们没有catch处理该异常的情况下,一定会走注释2的逻辑(被这个catch捕获),所以一定会走completeTransactionAfterThrowing(),同时第二个业务方法也不会调用到,所以只有一个事务分析起来比较简单
1-1.completeTransactionAfterThrowing()
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
if (logger.isTraceEnabled()) {
logger.trace("Completing transaction for [" + txInfo.getJoinpointIdentification() +
"] after exception: " + ex);
}
//默认的回滚策略是RuntimeException或者其子类,可以通过rollbackFor配置
//1.如果满足上面的回滚要求
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by rollback exception", ex);
throw ex2;
}
}
//2.不满足回滚策略,比如抛出Exception时,可自行实验
else {
// We don't roll back on this exception.
// Will still roll back if TransactionStatus.isRollbackOnly() is true.
try {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by commit exception", ex);
ex2.initApplicationException(ex);
throw ex2;
}
catch (RuntimeException | Error ex2) {
logger.error("Application exception overridden by commit exception", ex);
throw ex2;
}
}
}
}
比较简单,直接看注释把,接下来看txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus())
1-1-1.rollback()
@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);
}
1-1-1-1.processRollback
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
boolean unexpectedRollback = unexpected;
try {
triggerBeforeCompletion(status);
//1.判断是否有回滚点
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Rolling back transaction to savepoint");
}
status.rollbackToHeldSavepoint();
}
//2.只有status的newTransaction=true才有资格回滚
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction rollback");
}
doRollback(status);
}
//3.其他,让外面的一层事务回滚处理
else {
// Participating in larger transaction
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");
}
}
}
else {
logger.debug("Should roll back transaction but cannot - no transaction available");
}
// Unexpected rollback only matters here if we're asked to fail early
if (!isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = false;
}
}
}
catch (RuntimeException | Error ex) {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
throw ex;
}
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
// Raise UnexpectedRollbackException if we had a global rollback-only marker
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction rolled back because it has been marked as rollback-only");
}
}
finally {
cleanupAfterCompletion(status);
}
}
这里是第一个方法第一个注释,分下面两种情况
情况1:如果这个第一个事务就配置的传播属性是NESTED,那么是走到注释1,回滚到回滚点,也就是回滚到调用业务代码之前的回滚点,即回滚之后对数据库来说没有做任何事情,回滚代码其实就是jdbc的封装而已
public void rollbackToSavepoint(Object savepoint) throws TransactionException {
ConnectionHolder conHolder = getConnectionHolderForSavepoint();
try {
conHolder.getConnection().rollback((Savepoint) savepoint);
conHolder.resetRollbackOnly();
}
catch (Throwable ex) {
throw new TransactionSystemException("Could not roll back to JDBC savepoint", ex);
}
}
情况2:没有回滚点,代码走到注释2,此时newTransaction=true(不清楚的可以看上篇),所以有资格执行执行doRollback
protected void doRollback(DefaultTransactionStatus status) {
DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
Connection con = txObject.getConnectionHolder().getConnection();
if (status.isDebug()) {
logger.debug("Rolling back JDBC transaction on Connection [" + con + "]");
}
try {
con.rollback();
}
catch (SQLException ex) {
throw new TransactionSystemException("Could not roll back JDBC transaction", ex);
}
}
代码也是非常的简单,直接就是jdbc的回滚代码。到这里第一种代码回滚的情况分析完成了
2.第二个方法抛异常-第二个方法是REQUIRED传播属性
重复的代码不做分析了,根据上篇文章的分析,这种情况第二个事务的状态的newTransaction=false,所以是没有资格做回滚操作,那么它会将异常抛出去,那么最外层的事务,也就是第一个方法的事务处理
try {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
}
catch (TransactionSystemException ex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
//异常继续抛出
throw ex2;
}
那么就到了第一个方法处理异常了,这种情况就变成了和情况1一样了,就不继续分析了
3.第二个方法抛异常-第二个方法是REQUIRES_NEW传播属性
上面文章已经分析过了,这种情况的事务创建时第二个事务会将第一个事务挂起,第二个事务的newTransaction=true,但是第二个事务的connection和第一个事务的connection不是同一个。它是有资格回滚,所以它回滚的只是自己的业务部分的逻辑,回滚的操作和上面的处理逻辑一样,不另外说明。但是还有个重要的地方,第二个方法回滚成功后会唤醒第一个被挂起的事务,唤醒的逻辑是在cleanAfterCompletion(),其实这个方法在finally中,回滚都会走这个逻辑。
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
status.setCompleted();
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (status.isNewTransaction()) {
doCleanupAfterCompletion(status.getTransaction());
}
//1.判断这个对象是否为null
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion of inner transaction");
}
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
//2.唤醒
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
在上篇中,我们看到了,创建第二个事务时如果将第一个事务挂起,它会被封装到SuspendedResources,这里就能拿到它,然后唤醒。唤醒的逻辑我们基本上都能猜得到,跟挂起的逻辑相反,上篇文章中已经分析了,挂起时会将当前事务(第二个事务)的connectionHolder设置成null,另外删除threadlocal中的map,那么唤醒就是相反的逻辑了,感兴趣自行分析(重新往threadlocal中增加)。另外就是第二个方法抛异常会继续往上面抛,那就是第一层的catch里面也会捕获到异常,也会做相应的回滚操作,逻辑通上面一样。
4.第二个方法抛异常-第二个方法是NESTED传播属性
这种情况上篇文章分析过,第二个创建时(在执行业务逻辑之前)会创建回滚点,那么当第二个方法抛异常时,会回滚掉这个回滚点上。前面已经分析过回滚点回滚了,不再继续,另外需要注意的也是异常会往上抛,导致第一个方法也会回滚。
二、事务提交
事务提交分下面几种情况分析,其实理解了事务的回滚,再看事务的提交会更加的简单
1.第二个传播属性为REQUIRED
2.第二个传播属性为REQUIRES_NEW
3.第二个传播属性为NESTED
先看下面的提交的代码
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
boolean beforeCompletionInvoked = false;
try {
//提交之前的准备工作
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status);
triggerBeforeCompletion(status);
beforeCompletionInvoked = true;
//1.是否有回滚点
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
status.releaseHeldSavepoint();
}
//2.newTransaction=true才能提交
else if (status.isNewTransaction()) {
if (status.isDebug()) {
logger.debug("Initiating transaction commit");
}
unexpectedRollback = status.isGlobalRollbackOnly();
doCommit(status);
}
else if (isFailEarlyOnGlobalRollbackOnly()) {
unexpectedRollback = status.isGlobalRollbackOnly();
}
// Throw UnexpectedRollbackException if we have a global rollback-only
// marker but still didn't get a corresponding exception from commit.
if (unexpectedRollback) {
throw new UnexpectedRollbackException(
"Transaction silently rolled back because it has been marked as rollback-only");
}
}
catch (UnexpectedRollbackException ex) {
// can only be caused by doCommit
triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
throw ex;
}
catch (TransactionException ex) {
// can only be caused by doCommit
if (isRollbackOnCommitFailure()) {
doRollbackOnCommitException(status, ex);
}
else {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
}
throw ex;
}
catch (RuntimeException | Error ex) {
if (!beforeCompletionInvoked) {
triggerBeforeCompletion(status);
}
doRollbackOnCommitException(status, ex);
throw ex;
}
// Trigger afterCommit callbacks, with an exception thrown there
// propagated to callers but the transaction still considered as committed.
try {
triggerAfterCommit(status);
}
finally {
triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
}
}
finally {
cleanupAfterCompletion(status);
}
}
这个代码和上面回滚的很像,下面逐个分析
1.第二个传播属性为REQUIRED
这种情况由于第二个事务的newTransaction=false,所以没有提交的资格,必须由外层的事务提交,由于和外层的事务时同一个连接(不同的事务,但是是同一个连接),所以当外层由异常时,第二个方法做的sql操作也会被回滚掉。
2.第二个传播属性为REQUIRES_NEW
这种情况第一个事务和第二个事务的连接不同,并且第一个事务被挂起了,第二个事务的newTransaction=true。所以第二个事务提交完成后,不管第一个事务是否抛异常都不影响第二个事务的结果。还有一个需要注意的是,这个时候第二个事务提交完成后,也会将第一个挂起的事务进行恢复,逻辑同上面抛异常的恢复逻辑一致。所以第一个事务后面也会进行事务提交,提交的逻辑同上
3.第二个传播属性为NESTED
这种情况下,我们看下面的回滚的逻辑
if (status.hasSavepoint()) {
if (status.isDebug()) {
logger.debug("Releasing transaction savepoint");
}
unexpectedRollback = status.isGlobalRollbackOnly();
status.releaseHeldSavepoint();
}
第二个事务会清除回滚点,说明第二个业务操作没有问题。同时第一个事务和第二个事务的连接使用的是统一个连接,所以当第二个事务回滚完成后(清除了回滚点),假如第一个事务有异常,并且回滚时,第二个事务做的sql操作也会被回滚。
到这里事务的提交也算分析完成了,这里面只是分析了几种常见的情况。其真实的情况远不止这几种情况,但是分析的方法是一样的,大家可以自行分析。
三、事务的注意事项
如果没有彻底弄清事务的原理,有时候编写的事务并不能满足我们我们的需求,可能经常会遇到意想不到的情况,下面对常见的问题做个小的总结
1.嵌套调用本对象的方法,第二个方法事务不生效
伪代码如下:
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void afun(){
bfun();
}
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void bfun(){
}
此时bfun()的事务是不生效的,因为这里使用的cglib动态代理,当从afun()里面调用到bfun时就没有增强了,其调用的时真是的对象的bfun(),这种情况再spring aop也会遇到,解决方式可以通过自己注入自己,或者使用AopContext
2.务必注意只有事务状态newTransaction=true时才有资格提交或者回滚事务
这一点对我们分析事务时非常的有用
3.一定要注意异常会往上抛
这一点刚才在上面的分析中也有体现出来,当我们里层的事务抛出异常时,spring不会吞掉我们的异常,它会继续往上抛,这就导致外层的事务也会catch到异常,做相应的回滚操作,这一点尤其要小心。比如下面伪代码
@Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
public void cfun(){
//该方法的传播属为required
bservice.afun();
cservice.afun();
}
此时如果cservice.afun()方法抛出异常了,那么bservice.afun()执行的sql也不会生效
4.可以根据业务需求,使用catch处理异常不网上抛,来达到特定的业务需求
这种情况就是不让里层的事务影响到外层的事务,可以使用catch捕获异常,然后吞掉不往外抛出。如果有这种业务需求,可以考虑使用这种方法。
好了到这里就算简单的把注解式事务介绍完毕了,希望对大家有帮助。同时如果有错误的地方也欢迎大家指正!!!谢谢
发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/111197.html原文链接:https://javaforall.cn
【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛
【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...