@Transactional 传播行为及源码理解

在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_REQUIREDPROPAGATION_REQUIRES_NEW两种传播级别已经可以解决95%的场景,@Transactional注解要灵活运用。


记录成长,热爱生活!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值