1. 为什么需要深入理解Spring声明式事务
Spring框架中的声明式事务管理一直是Java企业级开发的核心组件之一。与编程式事务相比,声明式事务通过简单的注解配置就能实现复杂的事务控制逻辑,这背后究竟是如何运作的?我花了三周时间完整跟踪了Spring 6.1.13版本中声明式事务的源码执行链路,下面将完整呈现这个黑盒内部的运作机制。
在实际项目中,我们经常遇到这样的场景:明明加了@Transactional注解,事务却没有生效;或者在不同传播行为下,事务的边界出现了意料之外的情况。理解源码层面的实现原理,能够帮助我们快速定位这类问题,而不是靠猜测和反复试错。
2. 声明式事务的核心架构设计
2.1 事务管理的核心接口体系
Spring事务抽象的核心是PlatformTransactionManager接口体系。这个设计精妙的抽象层使得Spring可以支持各种数据访问技术的事务管理,无论是JDBC、JPA还是Hibernate。关键的三个接口是:
- TransactionDefinition:定义了事务的隔离级别、传播行为、超时时间等属性
- TransactionStatus:表示事务的运行时状态
- PlatformTransactionManager:事务操作的核心接口
java复制public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition definition);
void commit(TransactionStatus status);
void rollback(TransactionStatus status);
}
2.2 代理机制的实现选择
声明式事务基于AOP实现,Spring提供了两种代理方式:
- JDK动态代理:基于接口实现,要求目标类必须实现至少一个接口
- CGLIB代理:通过生成子类的方式实现,可以代理普通类
在Spring Boot 2.x之后,默认使用CGLIB代理。这是因为:
- 不需要强制要求接口
- 性能差距在现代JVM上已经不明显
- 可以代理final方法(虽然不推荐)
重要提示:如果你在Spring Boot应用中看到"CGLIB增强"的日志,但你的类实现了接口,这可能是因为你显式配置了proxyTargetClass=true。
3. @Transactional注解的完整处理流程
3.1 注解的解析与元数据准备
当Spring容器启动时,对于带有@Transactional注解的类或方法,InfrastructureAdvisorAutoProxyCreator这个Bean后处理器会识别它们,并为其创建代理。关键的解析过程在AnnotationTransactionAttributeSource中完成:
- 检查类级别注解
- 检查方法级别注解(优先级更高)
- 合并注解属性,生成TransactionAttribute对象
java复制protected TransactionAttribute parseTransactionAnnotation(AnnotationAttributes attributes) {
RuleBasedTransactionAttribute rbta = new RuleBasedTransactionAttribute();
// 解析传播行为
Propagation propagation = attributes.getEnum("propagation");
rbta.setPropagationBehavior(propagation.value());
// 解析隔离级别
Isolation isolation = attributes.getEnum("isolation");
rbta.setIsolationLevel(isolation.value());
// 其他属性解析...
return rbta;
}
3.2 事务拦截链的执行
当调用被@Transactional注解的方法时,实际执行的是TransactionInterceptor的invoke方法。这个拦截器是整个声明式事务的核心:
java复制public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取事务属性
TransactionAttribute txAttr = this.txAttrCache.get(invocation.getMethod());
// 获取事务管理器
PlatformTransactionManager tm = determineTransactionManager(txAttr);
// 处理事务
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 标准事务处理流程
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, invocation.getMethod(), invocation.getArguments());
Object retVal;
try {
// 执行业务方法
retVal = invocation.proceed();
} catch (Throwable ex) {
// 异常处理:回滚或提交
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
// 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
// 其他处理分支...
}
4. 事务传播行为的实现细节
4.1 七种传播行为的源码实现
Spring定义了七种事务传播行为,在AbstractPlatformTransactionManager中实现了这些行为的逻辑:
- REQUIRED(默认):如果当前存在事务,则加入该事务;否则新建一个事务
- REQUIRES_NEW:总是新建事务,如果当前存在事务,则挂起当前事务
- NESTED:如果当前存在事务,则在嵌套事务内执行;否则新建事务
- SUPPORTS:如果当前存在事务,则加入该事务;否则以非事务方式执行
- NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则挂起当前事务
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常
- MANDATORY:必须在一个已有的事务中执行,否则抛出异常
4.2 挂起与恢复事务的实现
当遇到REQUIRES_NEW或NOT_SUPPORTED等需要挂起当前事务的传播行为时,Spring会执行以下操作:
java复制protected final SuspendedResourcesHolder suspend(@Nullable Object transaction) {
// 1. 获取当前事务的所有资源
List<TransactionSynchronization> suspendedSynchronizations = TransactionSynchronizationManager.getSynchronizations();
// 2. 解绑所有资源
for (Map.Entry<Object, Object> entry : TransactionSynchronizationManager.getResourceMap().entrySet()) {
resources.put(entry.getKey(), entry.getValue());
TransactionSynchronizationManager.unbindResource(entry.getKey());
}
// 3. 清空同步器
TransactionSynchronizationManager.clearSynchronization();
// 4. 返回挂起的信息持有者
return new SuspendedResourcesHolder(transaction, resources, suspendedSynchronizations);
}
恢复事务时则执行相反的操作,将挂起的资源重新绑定到当前线程。
5. 事务的提交与回滚机制
5.1 事务提交的完整流程
事务提交并非简单的调用commit()方法那么简单,Spring在提交前后做了大量工作:
- 触发前置同步操作(TransactionSynchronization.beforeCommit)
- 执行实际提交(通过PlatformTransactionManager)
- 触发后置同步操作(TransactionSynchronization.afterCommit)
- 清理线程绑定资源
- 触发完成回调(无论提交还是回滚都会执行)
java复制private void processCommit(DefaultTransactionStatus status) throws TransactionException {
// 检查是否需要回滚
if (status.isLocalRollbackOnly() || status.isGlobalRollbackOnly()) {
processRollback(status, false);
return;
}
// 触发beforeCommit回调
triggerBeforeCommit(status);
// 执行实际提交
doCommit(status);
// 触发afterCommit回调
triggerAfterCommit(status);
// 清理资源
cleanupAfterCompletion(status);
}
5.2 回滚规则的判定逻辑
Spring不仅会在遇到RuntimeException时回滚事务,还支持通过@Transactional注解的rollbackFor/rollbackForClassName/noRollbackFor/noRollbackForClassName属性自定义回滚规则。判定逻辑在RuleBasedTransactionAttribute中实现:
java复制public boolean rollbackOn(Throwable ex) {
// 检查是否有自定义规则
if (this.rollbackRules != null) {
for (RollbackRuleAttribute rule : this.rollbackRules) {
if (rule.getDepth(ex) >= 0) {
return !(rule instanceof NoRollbackRuleAttribute);
}
}
}
// 默认规则:RuntimeException回滚,CheckedException不回滚
return (ex instanceof RuntimeException || ex instanceof Error);
}
6. 事务同步与资源管理
6.1 TransactionSynchronizationManager的工作原理
这个类使用ThreadLocal来管理事务资源,确保每个线程有独立的事务上下文。它维护了三个核心的ThreadLocal变量:
- resources:保存实际的事务资源(如数据库连接)
- synchronizations:事务同步回调接口集合
- currentTransactionName:当前事务名称
- currentTransactionReadOnly:当前事务是否只读
- currentTransactionIsolationLevel:当前事务隔离级别
- actualTransactionActive:当前是否有活跃事务
6.2 资源绑定的关键过程
以DataSourceUtils为例,获取连接时的资源绑定过程:
java复制public static Connection doGetConnection(DataSource dataSource) throws SQLException {
// 1. 尝试从当前事务上下文中获取已有连接
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && conHolder.hasConnection()) {
return conHolder.getConnection();
}
// 2. 没有则新建连接
Connection con = dataSource.getConnection();
// 3. 如果处于事务中,则绑定到当前线程
if (TransactionSynchronizationManager.isSynchronizationActive()) {
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
return con;
}
7. 常见问题与调试技巧
7.1 事务不生效的八大原因
根据源码分析,事务不生效通常有以下原因:
- 方法不是public的(Spring AOP的限制)
- 自调用问题(同一个类中方法A调用方法B,B的事务不生效)
- 异常被捕获未抛出
- 抛出的异常类型不符合回滚规则
- 数据库引擎不支持事务(如MyISAM)
- 没有正确配置事务管理器
- 传播行为设置不当
- 使用了final/static方法
7.2 调试事务问题的有效方法
- 开启Spring的debug日志:logging.level.org.springframework.transaction=DEBUG
- 检查代理是否生效:在Bean上调用getClass(),看是否是代理类
- 使用TransactionSynchronizationManager.isActualTransactionActive()判断当前是否有事务
- 检查TransactionSynchronizationManager的资源绑定情况
- 使用Spring的TransactionSynchronization接口注册回调,观察事务生命周期事件
java复制TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
@Override
public void afterCommit() {
System.out.println("事务已提交");
}
@Override
public void afterCompletion(int status) {
System.out.println("事务完成,状态:" + (status == STATUS_COMMITTED ? "已提交" : "已回滚"));
}
});
8. 性能优化与高级用法
8.1 事务超时的实现原理
@Transactional的timeout属性并非所有情况都有效,它的实现依赖于具体的事务管理器。以DataSourceTransactionManager为例:
- 在开始事务时,记录开始时间
- 每次操作前检查是否超时
- 超时后抛出TransactionTimedOutException
关键代码在JdbcTransactionObjectSupport中:
java复制public void setTimeoutInSeconds(int timeoutInSeconds) {
this.timeoutInSeconds = timeoutInSeconds;
if (timeoutInSeconds > 0) {
this.deadline = System.currentTimeMillis() + timeoutInSeconds * 1000;
}
}
public boolean hasTimeout() {
return (this.deadline > 0);
}
public boolean isTimeout() {
return (hasTimeout() && System.currentTimeMillis() > this.deadline);
}
8.2 只读事务的优化机制
当设置@Transactional(readOnly = true)时,Spring会进行一些优化:
- 对于Hibernate/JPA:刷新模式设置为MANUAL,避免不必要的flush
- 对于JDBC:设置连接为只读模式(connection.setReadOnly(true))
- 某些数据库会对只读连接进行优化
但要注意,这只是一个提示,数据库本身不会阻止只读事务中的写操作。真正的约束需要在应用层保证。
