当前位置: 首页 > news >正文

Spring 声明式事务:从原理到实现的完整解析

在后端开发中,事务管理是保证数据一致性的核心机制。尤其是在复杂业务场景下,一个操作可能涉及多步数据库操作,任何一步失败都需要回滚到初始状态。Spring 的声明式事务通过 AOP 思想,将事务管理从业务逻辑中剥离,让开发者更专注于核心业务。本文将结合实际实现,详解声明式事务的核心机制和设计思路。

一、为什么需要声明式事务?

在讨论实现之前,我们先明确一个问题:为什么要用声明式事务,而不是手动编写事务代码?

假设我们有一个 "下单" 业务,需要完成 3 步操作:

  1. 创建订单记录
  2. 扣减商品库存
  3. 扣除用户余额

如果用编程式事务(手动控制事务),代码可能是这样的:

// 编程式事务伪代码
public void createOrder() {Connection conn = null;try {conn = dataSource.getConnection();conn.setAutoCommit(false); // 开启事务// 1. 创建订单orderDao.insert(order);// 2. 扣减库存stockDao.decrease(stockId, quantity);// 3. 扣除余额userDao.decreaseBalance(userId, amount);conn.commit(); // 全部成功,提交事务} catch (Exception e) {conn.rollback(); // 任何一步失败,回滚事务} finally {conn.close();}
}

这种方式的问题很明显:

  • 代码侵入性强:事务管理代码(开启、提交、回滚)与业务逻辑混杂,可读性差
  • 重复劳动:每个需要事务的方法都要写一遍类似代码,易出错
  • 维护成本高:如果要修改事务传播行为或隔离级别,需逐个修改方法

而声明式事务通过注解 + AOP实现事务管理,上述代码可简化为:

// 声明式事务
@Transactional // 仅需一个注解
public void createOrder() {orderDao.insert(order);stockDao.decrease(stockId, quantity);userDao.decreaseBalance(userId, amount);
}

事务的开启、提交、回滚等操作由框架自动完成,开发者只需关注业务逻辑。这就是声明式事务的核心价值:解耦事务管理与业务逻辑,提高开发效率和代码可维护性

二、声明式事务的核心组件

一个完整的声明式事务机制,需要以下核心组件协同工作:1. 事务管理器体系:事务操作的 "大脑"

事务管理器是声明式事务的核心,负责事务的创建、提交、回滚。Spring 通过接口抽象 + 模板方法设计,实现了灵活的事务管理机制。

(1)顶层接口:PlatformTransactionManager

该接口定义了事务管理的 3 个核心操作,是所有事务管理器的 "规范":

public interface PlatformTransactionManager {// 1. 获取事务(根据配置创建新事务或加入现有事务)TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;// 2. 提交事务void commit(TransactionStatus status) throws TransactionException;// 3. 回滚事务void rollback(TransactionStatus status) throws TransactionException;
}

  • TransactionDefinition:描述事务的属性(传播行为、隔离级别、超时时间等)
  • TransactionStatus:表示当前事务的状态(是否活跃、是否已提交、是否需要回滚等)
(2)抽象实现:AbstractPlatformTransactionManager

该抽象类实现了PlatformTransactionManager接口,通过模板方法模式封装了事务管理的通用流程,子类只需实现具体的数据源操作。

核心逻辑如下(简化版):

public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {@Overridepublic final TransactionStatus getTransaction(TransactionDefinition definition) {// 1. 尝试获取现有事务(根据传播行为判断)Object existingTransaction = doGetExistingTransaction(definition);// 2. 根据传播行为处理事务(核心逻辑)if (existingTransaction != null) {// 存在现有事务,按传播行为处理(如REQUIRED、REQUIRES_NEW等)return handleExistingTransaction(definition, existingTransaction);}// 3. 不存在现有事务,创建新事务return startNewTransaction(definition);}@Overridepublic final void commit(TransactionStatus status) {try {// 1. 判断是否需要提交(如:是否已标记回滚)if (status.isRollbackOnly()) {doRollback(status);return;}// 2. 执行实际提交操作(由子类实现)doCommit(status);} catch (Exception e) {// 3. 提交失败,执行回滚doRollback(status);}}// 抽象方法:由子类实现具体的提交/回滚逻辑protected abstract void doCommit(TransactionStatus status);protected abstract void doRollback(TransactionStatus status);
}

模板方法的优势:将不变的流程(如判断传播行为、提交前检查)封装在父类,可变的部分(如具体数据库的提交操作)由子类实现,减少重复代码。

(3)具体实现:DataSourceTransactionManager

针对 JDBC 数据源的事务管理器,实现了AbstractPlatformTransactionManager的抽象方法,直接操作数据库连接:

public class DataSourceTransactionManager extends AbstractPlatformTransactionManager {private DataSource dataSource; // 数据源@Overrideprotected void doCommit(TransactionStatus status) {// 获取当前事务的数据库连接ConnectionHolder conHolder = (ConnectionHolder) status.getTransaction();Connection con = conHolder.getConnection();try {con.commit(); // 调用JDBC的commit()} catch (SQLException e) {throw new TransactionSystemException("提交事务失败", e);}}@Overrideprotected void doRollback(TransactionStatus status) {ConnectionHolder conHolder = (ConnectionHolder) status.getTransaction();Connection con = conHolder.getConnection();try {con.rollback(); // 调用JDBC的rollback()} catch (SQLException e) {throw new TransactionSystemException("回滚事务失败", e);}}
}

通过这种设计,Spring 可以轻松支持多种数据源(如 Hibernate、JPA、JTA 等),只需提供对应的PlatformTransactionManager实现类。

2. 事务传播机制:解决 "事务嵌套" 问题

当一个事务方法调用另一个事务方法时,如何处理事务关系?这就是事务传播行为要解决的问题。Spring 定义了 7 种传播行为,最常用的有 4 种:

传播行为含义典型场景
REQUIRED如果当前有事务,加入该事务;否则创建新事务(默认值)大多数业务方法(如下单 + 扣库存)
REQUIRES_NEW无论当前是否有事务,都创建新事务(新事务与原事务独立)日志记录(即使主事务失败也要记录)
SUPPORTS支持当前事务,如果没有事务则以非事务方式执行查询操作(可选事务)
MANDATORY必须在现有事务中执行,否则抛出异常子事务必须依赖父事务存在

示例:REQUIRED vs REQUIRES_NEW

// 方法A:使用REQUIRED
@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {// 操作1methodB(); // 调用方法B// 操作2
}// 方法B:使用REQUIRES_NEW
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {// 操作3
}

执行流程:

  • 调用methodA时,创建事务 T1
  • 执行到methodB时,因传播行为是 REQUIRES_NEW,暂停 T1,创建新事务 T2
  • methodB执行完毕,T2 提交
  • 回到methodA,恢复 T1,继续执行操作 2,最后 T1 提交

如果methodA的操作 2 失败,T1 回滚,但T2 已提交,不会回滚(两者是独立事务)。

3. 事务同步管理:线程安全的资源绑定

事务操作需要保证同一个线程内的数据库连接唯一(否则无法保证事务一致性)。例如:一个事务中,两次数据库操作必须使用同一个连接,才能保证在同一事务内。

TransactionSynchronizationManager通过ThreadLocal实现了 "线程 - 资源" 的绑定,核心原理:

public class TransactionSynchronizationManager {// 1. 绑定当前线程的事务资源(如:数据库连接)private static final ThreadLocal<Map<Object, Object>> resources = new ThreadLocal<>() {@Overrideprotected Map<Object, Object> initialValue() {return new HashMap<>();}};// 2. 绑定当前线程的事务状态private static final ThreadLocal<TransactionStatus> transactionStatus = new ThreadLocal<>();// 获取当前线程的资源(如:数据库连接)public static Object getResource(Object key) {return resources.get().get(key);}// 绑定资源到当前线程public static void bindResource(Object key, Object value) {resources.get().put(key, value);}// 解绑资源public static void unbindResource(Object key) {resources.get().remove(key);}
}

工作流程

  1. 开启事务时,DataSourceTransactionManager获取数据库连接,通过TransactionSynchronizationManager.bindResource(dataSource, connection)绑定到当前线程
  2. 事务内的所有数据库操作,都从当前线程获取同一个连接
  3. 事务提交 / 回滚后,解绑连接并归还到连接池

这样就保证了同一事务内的操作使用同一个连接,确保事务的 ACID 特性。

4. 声明式事务的 AOP 实现:注解驱动的事务管理

声明式事务的 "声明式" 体现在@Transactional注解,而注解的解析和事务逻辑的织入,依赖 AOP 实现。

核心组件是TransactionInterceptor(事务拦截器),它是一个 AOP 通知器,在目标方法执行前后织入事务逻辑:

public class TransactionInterceptor implements MethodInterceptor {private PlatformTransactionManager transactionManager;@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {// 1. 获取方法上的@Transactional注解,解析事务属性TransactionAttribute attr = getTransactionAttribute(invocation.getMethod());if (attr == null) {// 没有注解,直接执行方法return invocation.proceed();}// 2. 获取事务管理器PlatformTransactionManager tm = transactionManager;// 3. 获取事务状态TransactionStatus status = tm.getTransaction(attr);try {// 4. 执行目标方法(业务逻辑)Object result = invocation.proceed();// 5. 方法执行成功,提交事务tm.commit(status);return result;} catch (Exception e) {// 6. 方法执行失败,回滚事务tm.rollback(status);throw e;}}
}

执行流程

  1. 拦截带@Transactional注解的方法
  2. 解析注解中的事务属性(传播行为、隔离级别等)
  3. 调用事务管理器获取事务
  4. 执行目标方法(业务逻辑)
  5. 根据方法执行结果,提交或回滚事务

5. 事务隔离级别:解决并发问题

事务隔离级别定义了多个并发事务之间的可见性规则,用于解决并发场景下的脏读、不可重复读、幻读问题。

Spring 支持 5 种隔离级别(对应数据库的隔离级别):

隔离级别含义解决的问题
DEFAULT使用数据库默认隔离级别(如 MySQL 默认 REPEATABLE_READ)-
READ_UNCOMMITTED允许读取未提交的数据(最低隔离级别)无(会有脏读、不可重复读、幻读)
READ_COMMITTED只能读取已提交的数据脏读
REPEATABLE_READ保证同一事务内多次读取同一数据结果一致脏读、不可重复读
SERIALIZABLE事务串行执行(最高隔离级别,性能最低)所有问题

使用方式:通过@Transactionalisolation属性指定:

@Transactional(isolation = Isolation.READ_COMMITTED)
public void queryData() {// 业务逻辑
}

注意:隔离级别越高,并发性能越低,需根据业务场景平衡一致性和性能。例如:

  • 支付系统:需高一致性,可用 REPEATABLE_READ
  • 日志查询系统:可接受较低一致性,可用 READ_COMMITTED

三、声明式事务的使用注意事项

  1. 注解适用范围@Transactional可用于类或方法上,方法上的注解会覆盖类上的注解。

  2. 自调用失效问题:同一个类中的方法调用带@Transactional注解的方法,事务会失效(因为 AOP 无法拦截内部调用)。

    例如:

    public class OrderService {public void createOrder() {// 内部调用,@Transactional失效doCreateOrder(); }@Transactionalpublic void doCreateOrder() {// 业务逻辑}
    }
    

    解决办法:通过 Spring 上下文获取代理对象调用方法,或重构代码避免自调用。

  3. 异常回滚规则:默认情况下,只有未检查异常(RuntimeException 及其子类) 会触发回滚,检查异常(如 IOException)不会。可通过rollbackFor属性指定需要回滚的异常:

    @Transactional(rollbackFor = Exception.class) // 所有异常都回滚
    public void createOrder() {// 业务逻辑
    }
    
  4. 超时设置:通过timeout属性设置事务超时时间(秒),防止长事务占用资源:

    @Transactional(timeout = 30) // 超时30秒
    public void createOrder() {// 业务逻辑
    }
    

四、总结

声明式事务通过 "接口抽象 + 模板方法 + AOP+ThreadLocal" 的组合设计,实现了事务管理与业务逻辑的解耦。核心要点:

  1. 事务管理器PlatformTransactionManager定义事务操作规范,DataSourceTransactionManager等实现类适配具体数据源。
  2. 传播行为:控制事务嵌套关系,解决 "事务方法调用事务方法" 的场景。
  3. 同步管理:通过TransactionSynchronizationManager和 ThreadLocal,保证线程内资源唯一性。
  4. AOP 实现TransactionInterceptor拦截@Transactional注解,织入事务逻辑。
  5. 隔离级别:平衡并发性能和数据一致性,解决脏读、不可重复读等问题。

掌握声明式事务的原理,不仅能更灵活地使用 Spring 事务,还能理解 "接口抽象"、"模板方法"、"AOP" 等设计模式在实际场景中的应用。

如果这篇文章对大家有帮助可以点赞关注,你的支持就是我的动力😊!

http://www.dtcms.com/a/274170.html

相关文章:

  • 破解多宠管理难题,端侧AI重新定义宠物智能硬件
  • 《Spring 中上下文传递的那些事儿》Part 10:上下文敏感信息脱敏与审计日志记录
  • ESP32_启动日志分析
  • 【TCP/IP】17. 移动 IP
  • Linux权限的概念
  • 硬件加速(FPGA)
  • 函数指针指针函数 智能指针
  • 通过ETL工具,高效完成达梦数据库数据同步至数仓Oracle的具体实现
  • MDSE模型驱动的软件工程和敏捷开发相结合的案例
  • Django 视图(View)
  • 指令重排序带来的多线程问题与volatile解决方案
  • Linux设备树(dts/dtsi/dtb、设备树概念,设备树解析,驱动匹配)
  • P1204 [USACO1.2] 挤牛奶Milking Cows
  • 如何设置直播间的观看门槛,让直播间安全有效地运行?
  • 云原生周刊:镜像兼容性
  • 假日流量红利:如何用ASO策略抢占季节性下载高峰?
  • 不同质押周期对代币价格稳定性的具体影响及数据支撑
  • MinIO文件存储服务工具详细使用指南
  • 和服腰封改造:3种解构主义造型的东方美学新解
  • 2025年亚太中文赛赛题浅析-助攻快速选题
  • 【氮化镓】100 V GaN晶体管在关态应力下的双退化
  • Spring Boot中请求参数读取方式
  • HTTP 请求方法详解:GET、POST、PUT、DELETE 等
  • Python中类静态方法:@classmethod/@staticmethod详解和实战示例
  • LeetCode 278. 第一个错误的版本
  • 基于生产者消费者模型的线程池【Linux操作系统】
  • mysql中的自增ID
  • 物联网-ESP8266
  • API、MCP Client、MCP Server、LLM之间的业务逻辑关系
  • 医疗预约系统中的录音与图片上传功能实现:Vue3+Uniapp 实战