聊一聊Spring中的@Transactional注解【中】【事务传播特性】
前言
在开发过程中,我们常常会遇到这样的问题:当你在处理复杂的业务逻辑时,如何确保每一个操作都能顺利完成,而不会因为一个小小的错误而引发一场“数据灾难”?这就像在一场盛大的舞会上,你需要确保每位舞者都能完美配合,才能让整个表演毫无瑕疵。
这时,事务传播特性便像一位神秘的指挥家,默默地在幕后调控着这一切。它们帮助我们管理事务的生命周期,无论是让某个方法加入现有事务,还是在需要时优雅地挂起当前事务,甚至是独立开创一段新的舞蹈。不同的传播特性就像是各种舞步,灵活多变、各具特色。
一、样例
package com.lazy.snail.service;
import com.lazy.snail.dao.UserDao;
import com.lazy.snail.domain.UserInfo;
import com.lazy.snail.domain.UserLoginInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Timestamp;
/**
* @author lazysnail
*/
@Service
public class UserService {
private final UserDao userDao;
private final UserLoginService userLoginService;
public UserService(UserDao userDao, UserLoginService userLoginService) {
this.userDao = userDao;
this.userLoginService = userLoginService;
}
@Transactional(rollbackFor = Exception.class)
public void createUser(UserInfo user) throws UnknownHostException {
userDao.save(user);
UserLoginInfo userLoginInfo = new UserLoginInfo();
userLoginInfo.setId(user.getUserId());
userLoginInfo.setUserId(user.getUserId());
userLoginInfo.setIp(InetAddress.getLocalHost().getHostAddress());
userLoginInfo.setMac("");
userLoginInfo.setLoginTime(new Timestamp(System.currentTimeMillis()));
userLoginService.saveUserLoginInfo(userLoginInfo);
}
}
package com.lazy.snail.service;
import com.lazy.snail.dao.UserLoginDao;
import com.lazy.snail.domain.UserLoginInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @ClassName UserLoginService
* @Description TODO
* @Author lazysnail
* @Date 2024/11/1 10:43
* @Version 1.0
*/
@Service
public class UserLoginService {
private final UserLoginDao userLoginDao;
public UserLoginService(UserLoginDao userLoginDao) {
this.userLoginDao = userLoginDao;
}
@Transactional(rollbackFor = Exception.class)
public void saveUserLoginInfo(UserLoginInfo userLoginInfo) {
userLoginDao.save(userLoginInfo);
}
}
二、事务传播特性
2.1PROPAGATION_REQUIRED
支持当前事务;如果不存在则创建事务
- createUser和saveUserLoginInfo都有@Transactional注解
- createUser创建新事务tx1
- 由于存在tx1吗,saveUserLoginInfo不会创建新的事务,使用tx1
- saveUserLoginInfo出现异常,因在同一个事务,均回滚
/**
* saveUserLoginInfo拦截
*/
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 省略部分代码...
if (isExistingTransaction(transaction)) {
// 已存在事务,handleExistingTransaction中没有对PROPAGATION_REQUIRED进行特殊处理
return handleExistingTransaction(def, transaction, debugEnabled);
}
// 省略部分代码...
}
- createUser无@Transactional注解;saveUserLoginInfo有@Transactional注解
- createUser中的DML运行完就提交了
- saveUserLoginInfo会创建事务tx
- saveUserLoginInfo出现异常,不会回滚createUser中的DML
/**
* saveUserLoginInfo拦截
*/
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 省略部分代码...
// PROPAGATION_REQUIRED的处理
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 {
// 创建新事务tx
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
// 省略部分代码...
}
- createUser有@Transactional注解;saveUserLoginInfo无@Transactional注解
- createUser拦截后创建新事务tx
- saveUserLoginInfo中DML以非事务形式执行
- saveUserLoginInfo对于createUser只是一个普通方法
// MethodProxy
public Object invoke(Object obj, Object[] args) throws Throwable {
try {
init();
FastClassInfo fci = fastClassInfo;
// 目标方法调用 createUser
// 由于saveUserLoginInfo方法不会被拦截,createUser方法后直接运行了saveUserLoginInfo,没有任何关于事务的操作
return fci.f1.invoke(fci.i1, obj, args);
} catch (InvocationTargetException ex) {
throw ex.getTargetException();
} catch (IllegalArgumentException ex) {
if (fastClassInfo.i1 < 0)
throw new IllegalArgumentException("Protected method: " + sig1);
throw ex;
}
}
2.2PROPAGATION_SUPPORTS
支持当前事务;如果不存在则按照非事务执行
package com.lazy.snail.service;
import com.lazy.snail.dao.UserLoginDao;
import com.lazy.snail.domain.UserLoginInfo;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* @ClassName UserLoginService
* @Description TODO
* @Author lazysnail
* @Date 2024/11/1 10:43
* @Version 1.0
*/
@Service
public class UserLoginService {
private final UserLoginDao userLoginDao;
public UserLoginService(UserLoginDao userLoginDao) {
this.userLoginDao = userLoginDao;
}
// 传播特性指定为SUPPORTS
@Transactional(rollbackFor = Exception.class, propagation = Propagation.SUPPORTS)
public void saveUserLoginInfo(UserLoginInfo userLoginInfo) {
userLoginDao.save(userLoginInfo);
}
}
- createUser(Propagation.REQUIRED) && saveUserLoginInfo(Propagation.SUPPORTS)
- createUser创建了新事务tx1
- saveUserLoginInfo使用tx1
/**
* saveUserLoginInfo拦截
*/
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 省略部分代码...
if (isExistingTransaction(transaction)) {
// 已存在事务,handleExistingTransaction中没有对PROPAGATION_SUPPORTS进行特殊处理
return handleExistingTransaction(def, transaction, debugEnabled);
}
// 省略部分代码...
}
- createUser没有@Transactional && saveUserLoginInfo(Propagation.SUPPORTS)
- createUser中的DML以非事务形式运行
- saveUserLoginInfo被拦截
- saveUserLoginInfo中运行两次userLoginDao.save(userLoginInfo),第二次主键冲突异常失败,第一次被提交至数据库
- saveUserLoginInfo中以非事务形式运行
/**
* saveUserLoginInfo拦截
*/
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 省略部分代码...
// PROPAGATION_SUPPORTS满足以下条件,不会创建新事务
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 {
// 创建开启新事务tx
return startTransaction(def, transaction, debugEnabled, suspendedResources);
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
// 省略部分代码...
}
2.3PROPAGATION_MANDATORY
支持当前事务;如果不存在则抛出异常
- createUser没有@Transactional && saveUserLoginInfo(Propagation.MANDATORY)
- saveUserLoginInfo被拦截后发现没有事务,直接抛出异常
// AbstractPlatformTransactionManager
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
throw new IllegalTransactionStateException(
"No existing transaction found for transaction marked with propagation 'mandatory'");
}
}
2.4PROPAGATION_REQUIRES_NEW
创建新事务,如果存在事务则挂起
- createUser有@Transactional && saveUserLoginInfo(Propagation.REQUIRES_NEW)
- saveUserLoginInfo被拦截后发现有事务,挂起之前的事务
- 获取当前事务对象,包含事务的相关信息
- 修改事务的激活状态
- 保存当前事务的状态,如事务的传播特性、隔离级别等,便于后续能恢复到当前状态
- 从当前线程的上下文移除事务相关的信息
- 创建新的事务
- 新事务完成后,恢复挂起事务
- 从之前保存的状态中恢复事务的状态信息
- 更新事务的激活状态
- 恢复之前的上下文状态
- saveUserLoginInfo被拦截后发现有事务,挂起之前的事务
// AbstractPlatformTransactionManager
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW) {
// 挂起事务
SuspendedResourcesHolder suspendedResources = suspend(transaction);
try {
// 开启新的事务
return startTransaction(definition, transaction, debugEnabled, suspendedResources);
} catch (RuntimeException | Error beginEx) {
resumeAfterBeginException(transaction, suspendedResources, beginEx);
throw beginEx;
}
}
}
/**
* 新事物正常结束提交时,恢复之前的事务
*/
private void cleanupAfterCompletion(DefaultTransactionStatus status) {
status.setCompleted();
if (status.isNewSynchronization()) {
TransactionSynchronizationManager.clear();
}
if (status.isNewTransaction()) {
doCleanupAfterCompletion(status.getTransaction());
}
if (status.getSuspendedResources() != null) {
if (status.isDebug()) {
logger.debug("Resuming suspended transaction after completion of inner transaction");
}
Object transaction = (status.hasTransaction() ? status.getTransaction() : null);
// 恢复事务
resume(transaction, (SuspendedResourcesHolder) status.getSuspendedResources());
}
}
2.5PROPAGATION_NOT_SUPPORTED
不支持当前事务;始终以非事务形式执行
- createUser有@Transactional && saveUserLoginInfo(Propagation.NOT_SUPPORTED)
- saveUserLoginInfo被拦截后,挂起createUser的事务
- saveUserLoginInfo中的DML以非事务的形式执行
- 恢复createUser的事务
- createUser没有@Transactional && saveUserLoginInfo(Propagation.NOT_SUPPORTED)
- saveUserLoginInfo中的DML以非事务的形式执行
// AbstractPlatformTransactionManager
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NOT_SUPPORTED) {
if (debugEnabled) {
logger.debug("Suspending current transaction");
}
// 挂起事务,并没有开启新的事务
Object suspendedResources = suspend(transaction);
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(
definition, null, false, newSynchronization, debugEnabled, suspendedResources);
}
}
2.6PROPAGATION_NEVER
不支持当前事务;如果存在事务抛出异常
- createUser有@Transactional && saveUserLoginInfo(Propagation.NEVER)
- saveUserLoginInfo被拦截后,发现有活动的事务,直接抛出异常
- createUser没有@Transactional && saveUserLoginInfo(Propagation.NEVER)
- saveUserLoginInfo被拦截后,发现没有活动的事务,以非事务形式执行DML
// AbstractPlatformTransactionManager
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
// 抛出异常
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NEVER) {
throw new IllegalTransactionStateException(
"Existing transaction found for transaction marked with propagation 'never'");
}
}
2.7PROPAGATION_NESTED
如果存在事务,则以嵌套形式执行,否则像PROPAGATION_REQUIRED一样
- createUser有@Transactional && saveUserLoginInfo(Propagation.NESTED)
- saveUserLoginInfo被拦截后,发现已存在活动事务,创建一个新的嵌套事务(创建一个保存点)
- 共享外部事务的数据库连接
- 嵌套事务的回滚提交独立于外部事务
- createUser没有@Transactional && saveUserLoginInfo(Propagation.NESTED)
- saveUserLoginInfo被拦截后,发现没有活动事务
- 创建一个新事务,与PROPAGATION_REQUIRED相同
// AbstractPlatformTransactionManager
// 存在活动事务时
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
if (!isNestedTransactionAllowed()) {
throw new NestedTransactionNotSupportedException(
"Transaction manager does not allow nested transactions by default - " +
"specify 'nestedTransactionAllowed' property with value 'true'");
}
if (debugEnabled) {
logger.debug("Creating nested transaction with name [" + definition.getName() + "]");
}
if (useSavepointForNestedTransaction()) {
// 创建保存点
DefaultTransactionStatus status =
prepareTransactionStatus(definition, transaction, false, false, debugEnabled, null);
status.createAndHoldSavepoint();
return status;
} else {
return startTransaction(definition, transaction, debugEnabled, null);
}
}
}
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
// 没有活动事务时,PROPAGATION_NESTED与PROPAGATION_REQUIRED一样
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;
}
}
}
三、总结
3.1七大特性总结
传播特性 | 描述 | 适用场景 |
---|---|---|
PROPAGATION_REQUIRED | 加入现有事务或新建事务 | 最常用的特性 |
PROPAGATION_REQUIRES_NEW | 总是新建事务,挂起当前事务 | 独立执行某些操作 |
PROPAGATION_NESTED | 创建嵌套事务,依赖于保存点 | 复杂的业务逻辑,需要部分回滚 |
PROPAGATION_NOT_SUPPORTED | 非事务方式执行,挂起当前事务 | 确保在没有事务上下文中执行 |
PROPAGATION_NEVER | 非事务方式执行,若有事务则抛出异常 | 明确要求不使用事务 |
PROPAGATION_SUPPORTS | 加入事务或非事务方式执行 | 灵活的环境,支持可选事务 |
PROPAGATION_MANDATORY | 加入现有事务,若无事务则抛出异常 | 强制在事务上下文中运行 |
3.2事务挂起与嵌套事务的区别
特性 | 事务挂起 | 嵌套事务 |
---|---|---|
目的 | 暂时中断当前事务的执行 | 在一个事务上下文中创建一个独立的事务 |
状态管理 | 保持挂起事务的状态,稍后恢复 | 使用保存点管理嵌套事务的状态 |
事务提交/回滚 | 挂起后,当前事务继续执行,之后可以恢复 | 嵌套事务可以独立提交或回滚,外部事务不受影响 |
传播特性 | 适用于 PROPAGATION_NOT_SUPPORTED 或 PROPAGATION_REQUIRES_NEW | 使用 PROPAGATION_NESTED |