Spring 事务传播行为使用与源码分析
我们知道在 Java 项目当中,在一次的接口调用时可能存在多个 DML 行为,而每一次的 DML 行为都可以单独的作为一次事务,所以有了事务的传播行为我们可以更加细粒度的控制这些方法对数据所造成的影响。想要控制就可以把调用内容拆分成多个方法分配不同的传播行为。
Spring 支持以下的传播行为。
TransactionDefinition.PROPAGATION_REQUIRED
如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
当前传播行为的作用就是保证当前的方法调用链中至少存在一个事务并且只存在一个事务。
示例:
代码语言:typescript复制@RestController
public class AccountController {
@Autowired
private TestService testService;
@GetMapping("test1")
public void test1(){
System.out.println("当前版本" 1);
testService.test2();
testService.test3();
int i= 1/0;
}
}
@Service
public class TestService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private TestService testService;
public void test2(){
accountMapper.insert(new Account().setName(IdWorker.getIdStr()));
}
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
public void test3(){
accountMapper.insert(new Account().setName(IdWorker.getIdStr()));
testService.test4();
}
public void test4(){
accountMapper.insert(new Account().setEmail(IdWorker.getIdStr()));
int i = 1/0;
}
}
以上结果最终数据只会插入 1 条。我们来分析一下流程:首先 test1 方法没有事务,执行 test2 方法无异常插入一条记录,到 test3 方法开始有事务了,因为传播行为是 REQUIRED,并且因为无事务,所以会创建一个事务,然后正常插入一条数据,方法执行到 test4 时,插入一条数据后报错,并且这个是处于 test3 的调用链中的,所以把 test3 中插入的数据给回滚了。并且因为事务的起点在 test3 ,test1 插入的数据没有在 test3 的事务范围内,所以 test1 插入的数据不会回滚。
总结一下就是:当前传播行为从被标注的方法开始,到标注方法结束。中间不管标注了多少个事务注解也始终会被认为一个事务,数据一起回滚,一起提交。
然后我们分析一下源码实现,我们找到事务的核心代码位置,org.springframework.transaction.interceptor.TransactionAspectSupport#invokeWithinTransaction
...
// 创建事务
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
...
// 执行被代理的目标方法
retVal = invocation.proceedWithInvocation();
...
// 事务回滚
completeTransactionAfterThrowing(txInfo, ex);
...
// 事务提交
commitTransactionAfterReturning(txInfo);
然后看到 org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" def.getName() "]: " def);
}
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
上面代码开启事务。
当执行的代码再次遇到 @Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class)
标注的注解就会再次重新上面的流程,因为在 test3 中已经创建了事务,所以在重新执行上面流程中
if (isExistingTransaction(transaction)) {
// Existing transaction found -> check propagation behavior to find out how to behave.
return handleExistingTransaction(def, transaction, debugEnabled);
}
上面判断如果存在事务的话就只执行这块代码了。下面新建事务的代码就跳过去了。所以是存在事务加入事务,不存在事务新建事务。
TransactionDefinition.PROPAGATION_REQUIRES_NEW
创建一个新的事务,如果当前存在事务,则把当前事务挂起。
当前传播行为总是新建事务,如果外部没有事务,则当前方法会新建一个事务,如果之前有事务,则我也会创建一个事务,与原事务隔离。
示例:
代码语言:java复制@RestController
public class AccountController {
@Autowired
private TestService testService;
@GetMapping("test1")
public void test1(){
System.out.println("当前版本" 2);
testService.test1();
}
}
@Service
public class TestService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private TestService testService;
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void test1() {
accountMapper.insert(new Account().setAge(1));
testService.test2();
int i = 1 / 0;
}
@Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void test2() {
accountMapper.insert(new Account().setPhone(IdWorker.getIdStr()));
}
}
以上最终的结果只会插入一条数据。我们来分析一下代码。首先执行 test1 方法,因为标注了 REQUIRES_NEW 所以这时候会新建一个事务,然后新增一条记录,执行到 test2 的时候,因为也是标注了 REQUIRES_NEW 所以这时候也新建了一个事务,与原事务隔离,执行完新增一条记录。但是 test1 在最后执行出现异常开始回滚数据,但是因为 test2 的事务与 test1 的事务隔离,所以只回滚了 test1 的数据。
但是如果 test2 执行出现异常了,两个事务的数据都会回滚,test2 的数据回滚是好理解,但是为啥 test1 的事务也回滚了呢,因为 test2 在执行失败出现异常后
代码语言:php复制catch (Throwable ex) {
// target invocation exception
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
这里执行完回滚操作后会把异常往上抛,这样上一个事务也接收到了这个异常,自然也就回滚了。
TransactionDefinition.PROPAGATION_SUPPORTS
如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
当前传播行为为保持原状,有就加入,没有也执行。
示例:
代码语言:java复制@RestController
public class AccountController {
@Autowired
private TestService testService;
@GetMapping("test1")
public void test1(){
System.out.println("当前版本" 1);
testService.test1();
}
}
@Service
public class TestService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private TestService testService;
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public void test1() {
accountMapper.insert(new Account().setAge(1));
int i = 1 / 0;
}
}
@Service
public class TestService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private TestService testService;
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void test1() {
accountMapper.insert(new Account().setAge(1));
testService.test2();
}
@Transactional(propagation = Propagation.SUPPORTS, rollbackFor = Exception.class)
public void test2() {
accountMapper.insert(new Account().setAge(1));
int i = 1/0;
}
}
上面逻辑代码很简单就不分析了,直接看源码位置;
代码语言:scala复制if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
SuspendedResourcesHolder suspendedResources = suspend(null);
if (debugEnabled) {
logger.debug("Creating new transaction with name [" def.getName() "]: " def);
}
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
在源码位置没有 PROPAGATION_SUPPORTS 的逻辑,所以就不会创建事务了。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED
以非事务方式运行,如果当前存在事务,则把当前事务挂起。
当前传播行为总是无事务,有事务也不加入。
示例:
代码语言:java复制@RestController
public class AccountController {
@Autowired
private TestService testService;
@GetMapping("test1")
public void test1(){
System.out.println("当前版本" 3);
testService.test1();
}
}
@Service
public class TestService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private TestService testService;
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void test1() {
accountMapper.insert(new Account().setAge(1));
testService.test2();
}
@Transactional(propagation = Propagation.NOT_SUPPORTED, rollbackFor = Exception.class)
public void test2() {
accountMapper.insert(new Account().setPhone(IdWorker.getIdStr()));
int i = 1/0;
}
}
以上最终结果是插入一条数据。首先 test1 执行后创建一个事务,执行到 test2 时它会以非事务运行,数据不会回滚自然后插入一条数据了。而因为 test2 出现异常后异常上抛,test1 执行存在事务,然后数据就回滚了。最终插入一条数据。
TransactionDefinition.PROPAGATION_NEVER
以非事务方式运行,如果当前存在事务,则抛出异常。
当前事务行为总是无事务,有事务则当前方法中断执行。
示例:
代码语言:typescript复制@RestController
public class AccountController {
@Autowired
private TestService testService;
@GetMapping("test1")
public void test1(){
System.out.println("当前版本" 3);
testService.test1();
}
}
@Service
public class TestService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private TestService testService;
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void test1() {
accountMapper.insert(new Account().setAge(1));
testService.test2();
}
@Transactional(propagation = Propagation.NEVER, rollbackFor = Exception.class)
public void test2() {
accountMapper.insert(new Account().setPhone(IdWorker.getIdStr()));
}
}
最终结果是一条记录都插入不进去。我们主要分析一下为啥 test2 是以非事务运行但是记录居然没插入进去的问题。
看下源码解析,主要看 org.springframework.transaction.support.AbstractPlatformTransactionManager#handleExistingTransaction
位置当事务存在时的行为:
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
当存在事务时直接就抛出异常了,而程序的执行逻辑是先获取事务,然后才是执行目标方法,在执行目标方法前方法就出错了,自然而然记录就不会插入进去了。
TransactionDefinition.PROPAGATION_MANDATORY
如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
当前传播行为总是存在事务,如果不存在则中断执行。
示例:
代码语言:typescript复制@RestController
public class AccountController {
@Autowired
private TestService testService;
@GetMapping("test1")
public void test1(){
System.out.println("当前版本" 1);
testService.test1();
}
}
@Service
public class TestService {
@Autowired
private AccountMapper accountMapper;
@Autowired
private TestService testService;
@Transactional(propagation = Propagation.MANDATORY, rollbackFor = Exception.class)
public void test1() {
accountMapper.insert(new Account().setAge(1));
}
}
源码分析,在 org.springframework.transaction.support.AbstractPlatformTransactionManager#getTransaction
位置的时候判断是 PROPAGATION_MANDATORY 就直接抛出异常了。
// No existing transaction found -> check propagation behavior to find out how to proceed.
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
TransactionDefinition.PROPAGATION_NESTED
如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
在 REQUIRED 的传播行为当中,如果异常被捕获,最外层的事务还是会被回滚。
代码语言:c#复制@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void test1() {
accountMapper.insert(new Account().setAge(1));
try {
testService.test2();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void test2() {
accountMapper.insert(new Account().setPhone(IdWorker.getIdStr()));
int i = 1 / 0;
}
而在 PROPAGATION_REQUIRED 当中,test1 插入的数据能正常提交。
代码语言:c#复制 @Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public void test1() {
accountMapper.insert(new Account().setAge(1));
try {
testService.test2();
} catch (Exception e) {
e.printStackTrace();
}
}
@Transactional(propagation = Propagation.NESTED, rollbackFor = Exception.class)
public void test2() {
accountMapper.insert(new Account().setPhone(IdWorker.getIdStr()));
int i = 1 / 0;
}
我正在参与2023腾讯技术创作特训营第三期有奖征文,组队打卡瓜分大奖!