Spring 事务核心原理:从本质到源码的深度剖析
事务是保证数据一致性的核心机制,Spring 事务管理简化了传统 JDBC 事务的繁琐操作,通过 AOP 实现了声明式事务,让开发者无需关注事务的开启、提交和回滚细节。本次学习将从 Spring 事务的本质出发,深入解析其实现原理、核心源码,并结合动态数据源案例分析事务失效场景,彻底掌握 Spring 事务。
一、Spring 事务本质
1. Spring 事务的本质:AOP + 数据库事务
Spring 本身并不直接实现事务,而是对数据库事务的包装 ,通过 AOP 技术将事务管理逻辑(开启、提交、回滚)织入到业务方法中,实现声明式事务管理。
数据库事务: 底层依赖数据库的事务支持(如 MySQL 的 InnoDB 引擎通过 BEGIN、COMMIT、ROLLBACK 实现事务)。
Spring AOP: 通过动态代理拦截标注 @Transactional 的方法,在方法执行前后自动加入事务管理逻辑。
因此,Spring 事务 = 数据库事务 + Spring AOP 织入。
2. 事务核心概念
(1)Spring 事务管理
Spring 提供两种事务管理方式:
编程式事务: 通过 TransactionTemplate 手动编写事务逻辑(灵活性高,但侵入业务代码)。
声明式事务: 通过 @Transactional 注解或 XML 配置声明事务(非侵入式,推荐使用)。
声明式事务是开发中的主流,其核心是通过 AOP 自动管理事务生命周期。
(2)事务隔离级别
事务隔离级别定义了多个并发事务之间的可见性规则,解决脏读、不可重复读、幻读问题。Spring 支持以下隔离级别(对应数据库隔离级别):
| 隔离级别 | 说明 |
|---|---|
| DEFAULT | 采用数据库默认隔离级别(MySQL 默认 |REPEATABLE_READ,Oracle 默认 READ_COMMITTED)。 |
| READ_UNCOMMITTED | 最低隔离级别,允许读取未提交的数据(可能脏读、不可重复读、幻读)。 |
| READ_COMMITTED | 只能读取已提交的数据(解决脏读,可能不可重复读、幻读)。 |
| REPEATABLE_READ | 保证多次读取同一数据一致(解决脏读、不可重复读,可能幻读)。 |
| SERIALIZABLE | 最高隔离级别,事务串行执行(解决所有问题,但性能极差)。 |
隔离级别通过 @Transactional(isolation = Isolation.READ_COMMITTED) 设置。
(3)事务传播行为
当一个事务方法调用另一个事务方法时,传播行为定义了新事务的开启规则。Spring 定义了 7 种传播行为:
| 传播行为 | 说明 |
|---|---|
| REQUIRED(默认) | 若当前存在事务,则加入;否则创建新事务。 |
| SUPPORTS | 若当前存在事务,则加入;否则以非事务方式执行。 |
| MANDATORY | 必须在事务中执行,否则抛出异常。 |
| REQUIRES_NEW | 无论当前是否有事务,都创建新事务(原事务暂停)。 |
| NOT_SUPPORTED | 以非事务方式执行,若当前有事务则暂停。 |
| NEVER | 以非事务方式执行,若当前有事务则抛出异常。 |
| NESTED | 若当前有事务,则在嵌套事务中执行(仅部分数据库支持,如 MySQL 的 SAVEPOINT)。 |
传播行为通过 @Transactional(propagation = Propagation.REQUIRES_NEW) 设置,是解决事务嵌套问题的关键。
二、Spring 事务原理解析
1. Spring 事务处理基本流程
Spring 声明式事务的核心流程可分为三个阶段:
步骤 1:标记事务方法
在需要事务管理的方法或类上添加 @Transactional 注解,声明事务属性(隔离级别、传播行为等)。
java
@Service
public class UserService {
@Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED)
public void transferMoney(Long fromId, Long toId, BigDecimal amount) {
// 转账业务逻辑(扣钱 + 加钱)
}
}
步骤 2:Spring 启动时解析并生成代理
扫描注解: Spring 启动时,@EnableTransactionManagement 开启事务支持,通过 TransactionAnnotationParser 解析 @Transactional 注解。
生成代理: 对标注 @Transactional 的 Bean,通过 AOP 生成代理对象,代理逻辑包含事务的开启、提交、回滚。
步骤 3:运行时执行事务逻辑
当代理对象的事务方法被调用时,执行流程如下:
开启事务: 代理逻辑调用数据库 API(如 Connection.setAutoCommit(false))开启事务。
执行业务方法: 调用目标方法的核心业务逻辑(如转账)。
提交 / 回滚事务:
- 若业务方法正常执行,调用 Connection.commit() 提交事务。
- 若抛出异常(默认 RuntimeException 及其子类),调用 Connection.rollback() 回滚事务。
2. @Transactional 注解详解
(1)注解的作用
标识需要代理的方法: 告知 Spring 该方法需要被事务代理拦截,织入事务管理逻辑。
携带事务属性: 包含隔离级别、传播行为、超时时间、回滚规则等配置,用于事务管理。
(2)核心属性
java
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Transactional {
String value() default ""; // 指定事务管理器(多数据源时使用)
Propagation propagation() default Propagation.REQUIRED; // 传播行为
Isolation isolation() default Isolation.DEFAULT; // 隔离级别
int timeout() default -1; // 超时时间(秒,-1 表示默认)
boolean readOnly() default false; // 是否只读事务(优化查询性能)
Class<? extends Throwable>[] rollbackFor() default {}; // 需要回滚的异常类型
String[] rollbackForClassName() default {}; // 需回滚的异常类名
Class<? extends Throwable>[] noRollbackFor() default {}; // 不回滚的异常类型
}
(3)注解生效的关键配置
Spring 环境: 需通过 @EnableTransactionManagement 开启事务管理,该注解导入 TransactionManagementConfigurationSelector,注册事务相关的 BeanPostProcessor。
java
@Configuration
@EnableTransactionManagement // 开启事务支持
public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource); // 配置事务管理器
}
}
Spring Boot 环境: 自动配置类 TransactionAutoConfiguration 会默认开启事务支持(无需手动添加 @EnableTransactionManagement),并注册 DataSourceTransactionManager(若存在 DataSource Bean)。
3. 事务核心源码解析
(1)核心类与调用链路
Spring 事务的核心类包括:
- @EnableTransactionManagement:开启事务支持。
- TransactionInterceptor:事务拦截器(AOP 增强逻辑),负责事务的开启、提交、回滚。
- PlatformTransactionManager:事务管理器接口(如 DataSourceTransactionManager
实现类,对接数据库事务)。 - TransactionStatus:事务状态对象,记录事务的当前状态(是否新事务、是否已回滚等)。
调用链路图:
plaintext
代理对象.method()
→ TransactionInterceptor.invoke() // AOP拦截,事务入口
→ createTransactionIfNecessary() // 开启事务
→ PlatformTransactionManager.getTransaction() // 获取事务(开启或加入)
→ invokeJoinpoint() // 执行目标方法
→ commitTransactionAfterReturning() // 提交事务
→ completeTransactionAfterThrowing() // 异常时回滚
(2)关键源码分析
① 事务拦截器(TransactionInterceptor)
TransactionInterceptor 是事务 AOP 的核心增强器,其 invoke 方法拦截目标方法调用:
java
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 获取目标类和方法
Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);
// 执行事务逻辑
return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
}
}
invokeWithinTransaction 方法是事务管理的核心:
java
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// 1. 获取事务属性(@Transactional 注解配置)
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
// 2. 获取事务管理器(如 DataSourceTransactionManager)
final PlatformTransactionManager tm = determineTransactionManager(txAttr);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
// 3. 处理事务逻辑
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 3.1 开启事务(或加入现有事务)
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 3.2 执行目标方法(业务逻辑)
retVal = invocation.proceedWithInvocation();
} catch (Throwable ex) {
// 3.3 异常时回滚事务
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
} finally {
// 3.4 清理事务信息
cleanupTransactionInfo(txInfo);
}
// 3.5 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
} else {
// 处理 CallbackPreferringPlatformTransactionManager 逻辑(略)
}
}
② 开启事务(createTransactionIfNecessary)
该方法通过事务管理器获取事务,根据传播行为决定开启新事务或加入现有事务:
java
protected TransactionInfo createTransactionIfNecessary(
PlatformTransactionManager tm, TransactionAttribute txAttr, final String joinpointIdentification) {
// 省略非关键代码...
// 调用事务管理器获取事务
TransactionStatus status = tm.getTransaction(txAttr);
// 封装事务信息
return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}
DataSourceTransactionManager 的 getTransaction 方法最终调用数据库 API 开启事务:
java
@Override
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
// 获取数据库连接
Connection con = DataSourceUtils.getConnection(obtainDataSource());
// 设置自动提交为 false(开启事务)
con.setAutoCommit(false);
// 创建事务状态对象
DefaultTransactionStatus status = new DefaultTransactionStatus(
new ConnectionHolder(con), true, false, definition.isReadOnly(), false, null);
// 绑定事务到当前线程(ThreadLocal)
prepareSynchronization(status, definition);
return status;
}
③ 提交与回滚
提交事务: commitTransactionAfterReturning 调用事务管理器的 commit 方法:
java
protected void commitTransactionAfterReturning(@Nullable TransactionInfo txInfo) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
回滚事务: completeTransactionAfterThrowing 调用 rollback 方法:
java
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
if (txInfo != null && txInfo.getTransactionStatus() != null) {
// 判断是否需要回滚(根据 rollbackFor 配置)
if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
} else {
// 不需要回滚则提交
txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
}
}
}
三、动态数据源案例分析
动态数据源(如主从分离)是常见场景,需在事务中保证数据源切换的正确性。以下案例分析主从数据源切换与事务嵌套的实现。
1. 案例特点
主从分离: 写操作(如新增、更新)使用主库,读操作使用从库。
事务嵌套: 外层事务调用内层事务方法,需保证数据源一致性(如内层方法也使用主库)。
2. 实现步骤
(1)数据库准备
主库(master): 负责写操作,配置主从同步。
从库(slave): 负责读操作,同步主库数据。
(2)工程配置
① 配置动态数据源
定义 DynamicDataSource 继承 AbstractRoutingDataSource,通过 ThreadLocal 存储当前数据源标识:
java
public class DynamicDataSource extends AbstractRoutingDataSource {
// 线程本地变量:存储当前数据源标识("master" 或 "slave")
private static final ThreadLocal<String> CURRENT_DS = new ThreadLocal<>();
public static void setDataSource(String dataSource) {
CURRENT_DS.set(dataSource);
}
public static void clearDataSource() {
CURRENT_DS.remove();
}
@Override
protected Object determineCurrentLookupKey() {
return CURRENT_DS.get() == null ? "master" : CURRENT_DS.get();
}
}
② 配置数据源与事务管理器
java
@Configuration
public class DataSourceConfig {
// 主库数据源
@Bean
@ConfigurationProperties("spring.datasource.master")
public DataSource masterDataSource() {
return DruidDataSourceBuilder.create().build();
}
// 从库数据源
@Bean
@ConfigurationProperties("spring.datasource.slave")
public DataSource slaveDataSource() {
return DruidDataSourceBuilder.create().build();
}
// 动态数据源
@Bean
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSources = new HashMap<>();
dataSources.put("master", masterDataSource());
dataSources.put("slave", slaveDataSource());
dynamicDataSource.setTargetDataSources(dataSources);
dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
return dynamicDataSource;
}
// 事务管理器(使用动态数据源)
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
③ 定义数据源切换注解与 AOP
java
// 数据源切换注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value() default "master"; // "master" 或 "slave"
}
// AOP 切面:切换数据源
@Aspect
@Component
public class DataSourceAspect {
@Before("@annotation(dataSource)")
public void before(DataSource dataSource) {
DynamicDataSource.setDataSource(dataSource.value());
}
@After("@annotation(dataSource)")
public void after() {
DynamicDataSource.clearDataSource();
}
}
(3)业务代码与事务嵌套
java
@Service
public class OrderService {
@Autowired
private OrderMapper orderMapper;
@Autowired
private LogService logService;
// 主库事务:创建订单(写操作)
@Transactional
@DataSource("master")
public void createOrder(Order order) {
orderMapper.insert(order);
// 调用日志服务(嵌套事务,需使用主库)
logService.logOrderCreate(order.getId());
}
}
@Service
public class LogService {
@Autowired
private LogMapper logMapper;
// 嵌套事务:记录日志(写操作,需使用主库)
@Transactional(propagation = Propagation.REQUIRED) // 加入外层事务
@DataSource("master")
public void logOrderCreate(Long orderId) {
logMapper.insert(new Log(orderId, "order_created"));
}
}
(4)测试与 DEBUG 观察
测试场景: 调用 createOrder 方法,观察是否在主库创建订单和日志。
DEBUG 关键点:
- 进入 createOrder 时,DynamicDataSource 的 determineCurrentLookupKey() 返回"master"。
- 事务管理器开启事务,绑定主库连接到当前线程。
- 调用 logOrderCreate 时,传播行为 REQUIRED 使内层方法加入外层事务,数据源保持 "master"。
- 若 createOrder 抛出异常,订单和日志均回滚,主库无数据插入。
四、Spring 事务失效场景分析
事务失效是开发中常见问题,本质是事务代理逻辑未被触发或事务配置不符合预期。以下是典型场景:
1. 非 public 方法
Spring 事务代理默认只拦截 public 方法 ,非 public 方法(如 private、protected)的 @Transactional 注解会被忽略。
原因: Spring AOP 对非 public 方法的拦截存在限制(JDK 动态代理基于接口,CGLIB 虽可代理类,但默认不拦截非 public 方法)。
解决: 将方法改为 public,或通过自定义 AOP 配置强制拦截非 public 方法(不推荐,破坏封装)。
2. 数据库不支持事务
若数据库引擎不支持事务(如 MySQL 的 MyISAM 引擎),即使配置了 Spring 事务,也无法保证 ACID 特性。
解决: 使用支持事务的引擎(如 InnoDB)。
3. 调用本类方法(自调用)
同一类中,非事务方法调用事务方法时,事务失效。例如:
java
@Service
public class UserService {
// 非事务方法
public void updateUser(Long id) {
// 自调用:直接调用本类的事务方法
doUpdate(id);
}
// 事务方法(此处事务失效)
@Transactional
public void doUpdate(Long id) {
// 业务逻辑
}
}
原因: 自调用时,方法调用不会经过代理对象,事务拦截器无法拦截,因此事务逻辑不生效。
解决:
- 通过 AopContext.currentProxy() 获取代理对象,再调用事务方法:
java
public void updateUser(Long id) {
((UserService) AopContext.currentProxy()).doUpdate(id); // 走代理,事务生效
}
- 开启 @EnableTransactionManagement(exposeProxy = true) 暴露代理对象。
4. 抛出非运行时异常
默认情况下,Spring 事务仅对 RuntimeException 及其子类 回滚,若抛出受检异常(如 IOException),事务不会回滚。
示例:
java
@Transactional
public void transfer() throws IOException {
// 业务逻辑
throw new IOException("IO异常"); // 事务不回滚
}
解决: 通过 rollbackFor 指定需要回滚的异常类型:
java
@Transactional(rollbackFor = Exception.class) // 所有异常都回滚
public void transfer() throws IOException { ... }
5. 多线程调用
多线程中,子线程的事务与主线程的事务独立,主线程异常不会导致子线程事务回滚,反之亦然。
java
@Transactional
public void process() {
// 主线程逻辑
new Thread(() -> {
// 子线程事务(独立)
subService.doSomething();
}).start();
throw new RuntimeException("主线程异常"); // 主线程回滚,但子线程事务已提交
}
原因: 事务通过 ThreadLocal 绑定到当前线程,子线程无法共享主线程的事务上下文。
解决: 避免在事务中使用多线程,或通过分布式事务协调(如 Seata)。
6. 错误配置事务管理器
多数据源场景下,若未指定 @Transactional(value = "txManager2"),可能使用默认事务管理器,导致事务失效。
解决: 明确指定事务管理器,确保与数据源匹配。
总结
Spring 事务的本质是基于 AOP 对数据库事务的包装 ,通过 @Transactional 注解声明事务属性,由 TransactionInterceptor 拦截方法调用,自动完成事务的开启、提交和回滚。
核心要点:
事务属性: 隔离级别解决并发问题,传播行为控制事务嵌套。
源码核心: TransactionInterceptor 是事务拦截入口,PlatformTransactionManager 对接数据库事务。
失效场景: 非 public 方法、自调用、异常类型不匹配等场景会导致事务失效,需特别注意。
掌握 Spring 事务原理,不仅能避免开发中的坑,更能在复杂场景(如动态数据源、分布式事务)中设计可靠的事务方案。