浅聊Spring的事务传播行为
Spring 中的事务传播行为
事务管理并非 Spring 首创,Spring 也借鉴了很多其他的框架,然后加以统一。
在 Spring 中,我们经常使用声明式事务,在方法或类上添加 Spring 的 @Transtional 注解,在这个注解中我们可以指定事务传播行为,这个注解也参考了 EJB 的 javax.ejb.TransactionAttribute 以及 JTA 的 javax.transaction.Transactional,这里先通过对比认识一下这三者的异同。
参数\注解 | Spring @Transactional | EJB @TransactionAttribute | JTA @Transactional |
事务管理器 |
transactionManager | ||
传播行为 |
propagation | value | value |
隔离级别 |
isolation | ||
超时时间 |
timeout | ||
是否只读 |
readOnly | ||
回滚异常 | rollbackFor、rollbackForClassName | rollbackOn | |
不回滚异常 | noRollbackFor、noRollbackForClassName | dontRollbackOn |
从上面的表格中可以看到,在 Spring 的 @Transactional 中都可以找到 EJB、JTA 注解中相应的参数。事实上,Spring 也对 EJB 的 @TransactionAttribute 注解及 JTA 的 @Transactional 加以了支持,在 Spring 中这三个注解都可以使用。
现在将重点转向事务传播行为,上面的三个注解都有事务传播行为,那么这三者的事务传播行为又有何异同呢?
传播行为\定义位置 | Spring Propagation | EJB TransactionAttributeType | JTA TxType |
需要事务 | REQUIRED | REQUIRED | REQUIRED |
支持事务 | SUPPORTS | SUPPORTS | SUPPORTS |
强制事务 | MANDATORY | MANDATORY | MANDATORY |
需要新事务 | REQUIRES_NEW | REQUIRES_NEW | REQUIRES_NEW |
不支持事务 | NOT_SUPPORTED | NOT_SUPPORTED | NOT_SUPPORTED |
从不使用事务 | NEVER | NEVER | NEVER |
嵌套事务 | NESTED |
有没有发现一些问题?Spring 中的事务定义与 EJB、JTA 基本一致,它们名称不仅相同,事实上语义和实现也相似,而且 Spring 还增加了一个 NESTED 类型的事务传播行为。
事务传播行为主要是控制新方法执行时是否使用事务,如何处理线程中以存在的事务。下面是对 Spring 中的这7中事务传播行为的描述:
- REQUIRED:默认的事务传播行为;需要事务:存在事务则使用已存在事务,否则创建新的事务;
- SUPPORTS:支持已存在事务:存在事务则使用已存在事务,否则以非事务方式运行;
- MANDATORY:强制使用事务:存在事务则使用已存在事务,否则抛出异常;
- REQUIRES_NEW:需要新事务:存在事务则挂起已存在事务,否则创建新事务;
- NOT_SUPPORTED:不支持事务:存在事务则挂起已存在事务,否则以非事务方式运行;
- NEVER:从不使用事务:存在事务则抛出异常,否则以非事务方式运行;
- NESTED:嵌套事务:存在事务则使创建保存点使用嵌套的事务,否则创建新的事务。
Spring 事务传播行为实现
Spring 事务管理的核心接口是 PlatformTransactionManager,这个接口提供了获取事务、提交事务、回滚事务的方法,子类 AbstractPlatformTransactionManager 提供模板方法,提供了事务管理的主要实现。事务传播行为用于控制事务的获取,因此查看事务获取的相关源码即可了解其实现:
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager, Serializable {
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException {
TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
// 获取事务
Object transaction = doGetTransaction();
... 省略日志相关代码
if (isExistingTransaction(transaction)) {
// 事务是一个已存在的活动事务,根据事务隔离级别处理事务
return handleExistingTransaction(def, transaction, debugEnabled);
}
... 省略校验相关代码
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);
... 省略日志相关代码
try {
return startTransaction(def, transaction, debugEnabled, suspendedResources);
} catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
} else {
... 省略日志相关代码
boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
}
}
}
这段代码先获取事务对象,然后根据事务是否为线程上下文中已存在的事务执行不同的动作。线程上下文中不存在事务的逻辑已经在上面的代码中体现,存在事务的逻辑也类似,与事务传播行为的定义保持一致。我这里画了一张图便于理解:
总结
Spring 定义了不同的事务传播行为,用于指定获取 TransactionStatus 时的行为,到底使用线程上下文中已有的事务对象,还是创建新的事务对象,还是抛出异常。
Spring 获取的获取事务对象关联着线程上下文中保存的资源对象,对于 JDBC 来说就是 Connection,因此只要获取到 Spring 在线程上下文中保存的资源对象就可以接入 Spring 的事务管理,Spring JDBC、MyBatis 都是这个原理。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现