Spring事务原理探索

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))开启事务。
执行业务方法: 调用目标方法的核心业务逻辑(如转账)。
提交 / 回滚事务:

  1. 若业务方法正常执行,调用 Connection.commit() 提交事务。
  2. 若抛出异常(默认 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 事务的核心类包括:

  1. @EnableTransactionManagement:开启事务支持。
  2. TransactionInterceptor:事务拦截器(AOP 增强逻辑),负责事务的开启、提交、回滚。
  3. PlatformTransactionManager:事务管理器接口(如 DataSourceTransactionManager
    实现类,对接数据库事务)。
  4. 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 关键点:

  1. 进入 createOrder 时,DynamicDataSource 的 determineCurrentLookupKey() 返回"master"。
  2. 事务管理器开启事务,绑定主库连接到当前线程。
  3. 调用 logOrderCreate 时,传播行为 REQUIRED 使内层方法加入外层事务,数据源保持 "master"。
  4. 若 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) {
        // 业务逻辑
    }
}

原因: 自调用时,方法调用不会经过代理对象,事务拦截器无法拦截,因此事务逻辑不生效。

解决:

  1. 通过 AopContext.currentProxy() 获取代理对象,再调用事务方法:
java 复制代码
public void updateUser(Long id) {
    ((UserService) AopContext.currentProxy()).doUpdate(id); // 走代理,事务生效
}
  1. 开启 @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 事务原理,不仅能避免开发中的坑,更能在复杂场景(如动态数据源、分布式事务)中设计可靠的事务方案。

相关推荐
钟离墨笺4 小时前
Go语言-->sync.WaitGroup 详细解释
开发语言·后端·golang
Python私教4 小时前
深入理解 Java 中的 `while` 循环:原理、用法与实战技巧
后端
四念处茫茫4 小时前
仓颉技术:FFI外部函数接口
开发语言·后端·仓颉技术
Python私教4 小时前
深入理解 Java 分支语句:从基础到最佳实践
java·后端
艾菜籽4 小时前
MyBatis动态sql与留言墙联系
java·数据库·sql·spring·mybatis
初级程序员Kyle4 小时前
开始改变第五天 Java并发(1)
java
CodeSheep4 小时前
稚晖君官宣,全球首个0代码机器人创作平台来了!
前端·后端·程序员
yunxi_054 小时前
Kafka在企业级RAG系统中的最佳实践:从消息可靠性到异步流水线
后端·架构
yinke小琪4 小时前
面试官:如何决定使用 HashMap 还是 TreeMap?
java·后端·面试