Spring框架入门:TX 声明式事务详解

引言

在企业级 Java 应用中,数据一致性是核心要求之一。例如:用户转账操作必须保证"扣款"和"入账"同时成功或同时失败,否则将导致资金错乱。

为解决这一问题,数据库提供了 事务(Transaction) 机制。而 Spring 框架通过 声明式事务(Declarative Transaction) ,让我们无需编写繁琐的 try-catch-finallycommit/rollback 代码,仅通过 注解或 XML 配置 即可实现事务管理。

本文将系统讲解:

  1. 什么是声明式事务?与编程式事务有何区别?
  2. @Transactional 注解的 7 大核心属性详解
  3. 事务传播行为(Propagation)实战分析(REQUIRED、REQUIRES_NEW 等)
  4. 事务隔离级别(Isolation)与脏读、不可重复读、幻读
  5. Spring 事务底层原理:AOP + PlatformTransactionManager
  6. 常见陷阱:自调用失效、异常不回滚、只读事务等
  7. XML 配置方式(了解)与最佳实践

无论你是刚接触 Spring 的新手,还是希望深入理解事务机制的开发者,本文都将为你提供清晰、实用且深入的指导。


第一章:事务基础与 Spring 事务模型

1.1 什么是事务?

事务是一组数据库操作 ,具备 ACID 特性:

  • Atomicity(原子性):全部成功,或全部失败;
  • Consistency(一致性):事务前后数据状态合法;
  • Isolation(隔离性):并发事务互不干扰;
  • Durability(持久性):提交后结果永久保存。

1.2 编程式事务 vs 声明式事务

类型 说明 优缺点
编程式事务 手动调用 transactionManager.getTransaction()commit()rollback() ❌ 代码侵入性强,重复样板多
声明式事务 通过 @Transactional 或 XML 声明,由 Spring 自动管理 ✅ 无侵入、简洁、易维护

📌 Spring 推荐使用声明式事务


第二章:@Transactional 注解详解

2.1 基本用法

复制代码
@Service
public class AccountService {

    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        accountDao.debit(fromId, amount);   // 扣款
        accountDao.credit(toId, amount);    // 入账
        // 若此处抛出异常,整个方法回滚
    }
}

✅ 只需一个注解,Spring 自动处理事务开启、提交、回滚。


2.2 七大核心属性

@Transactional 提供多个属性,用于精细控制事务行为:

属性 类型 默认值 说明
propagation Propagation REQUIRED 事务传播行为
isolation Isolation DEFAULT 事务隔离级别
timeout int -1(无超时) 事务超时时间(秒)
readOnly boolean false 是否只读事务
rollbackFor Class[] 指定哪些异常触发回滚
noRollbackFor Class[] 指定哪些异常不回滚
value / transactionManager String "" 指定事务管理器 Bean 名称
示例:高级配置
复制代码
@Transactional(
    propagation = Propagation.REQUIRES_NEW,
    isolation = Isolation.READ_COMMITTED,
    timeout = 30,
    readOnly = false,
    rollbackFor = {SQLException.class, BusinessException.class},
    noRollbackFor = {ValidationException.class}
)
public void complexOperation() {
    // ...
}

第三章:事务传播行为(Propagation)

这是 最常被问到、也最容易出错 的知识点!

3.1 七种传播行为

传播行为 含义 典型场景
REQUIRED(默认) 如果当前存在事务,则加入;否则新建 大多数业务方法
SUPPORTS 如果当前存在事务,则加入;否则以非事务方式执行 查询类方法
MANDATORY 必须在事务中执行,否则抛异常 内部核心逻辑
REQUIRES_NEW 挂起当前事务,新建独立事务 日志记录、审计(需独立提交)
NOT_SUPPORTED 挂起当前事务,以非事务方式执行 调用外部非事务服务
NEVER 不能在事务中执行,否则抛异常 幂等性校验
NESTED 如果当前存在事务,则嵌套执行(支持部分回滚) 需要保存点(Savepoint)的场景

💡 重点掌握:REQUIRED、REQUIRES_NEW、NESTED


3.2 实战对比:REQUIRED vs REQUIRES_NEW

场景:用户注册 + 发送欢迎邮件
复制代码
@Service
public class UserService {

    @Autowired
    private EmailService emailService;

    @Transactional
    public void register(User user) {
        userDao.save(user);           // (1)
        emailService.sendWelcomeEmail(user); // (2)
    }
}

@Service
public class EmailService {

    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void sendWelcomeEmail(User user) {
        // 发送邮件(可能失败,但不应影响用户注册)
        emailClient.send(...);
        emailLogDao.save(...); // 记录发送日志
    }
}
  • sendWelcomeEmail 抛异常:
    • REQUIRED:用户注册和邮件都回滚 ❌
    • REQUIRES_NEW:用户注册成功,邮件日志回滚 ✅

REQUIRES_NEW 适用于"独立成功"的子操作


3.3 NESTED 嵌套事务(需数据库支持 Savepoint)

复制代码
@Transactional
public void batchProcess() {
    for (Item item : items) {
        try {
            processItem(item); // 方法内使用 NESTED
        } catch (Exception e) {
            // 单个 item 失败,不影响其他 item
            log.error("处理失败", e);
        }
    }
}

@Transactional(propagation = Propagation.NESTED)
public void processItem(Item item) {
    // ...
}

⚠️ 注意:MySQL InnoDB 支持 Savepoint,但 Oracle、PostgreSQL 行为略有差异。


第四章:事务隔离级别(Isolation)

4.1 四大并发问题

问题 描述 隔离级别解决
脏读(Dirty Read) 读到未提交的数据 READ_COMMITTED+
不可重复读(Non-repeatable Read) 同一事务内多次读取结果不同(数据被修改) REPEATABLE_READ+
幻读(Phantom Read) 同一事务内多次查询返回行数不同(新增/删除) SERIALIZABLE

4.2 Spring 隔离级别枚举

隔离级别 数据库对应 说明
DEFAULT 使用数据库默认(如 MySQL=REPEATABLE_READ) 推荐
READ_UNCOMMITTED 最低 允许脏读
READ_COMMITTED Oracle 默认 避免脏读
REPEATABLE_READ MySQL 默认 避免脏读 + 不可重复读
SERIALIZABLE 最高 完全串行,性能差

一般使用 DEFAULT 即可,除非有特殊需求。


第五章:Spring 事务底层原理

5.1 核心组件

  • PlatformTransactionManager :事务管理器接口(如 DataSourceTransactionManager
  • TransactionInterceptor:事务拦截器(AOP 通知)
  • @EnableTransactionManagement:启用事务注解支持

5.2 工作流程

  1. Spring 启动时,扫描 @Transactional 方法;
  2. 通过 AOP 创建代理对象(JDK/CGLIB);
  3. 调用方法时,TransactionInterceptor 拦截:
    • 开启事务(getTransaction()
    • 执行目标方法
    • 成功 → 提交(commit()
    • 异常 → 回滚(rollback()

🔍 本质:基于 AOP 的环绕通知(@Around)


5.3 启用事务支持

方式1:注解驱动(推荐)
复制代码
@Configuration
@EnableTransactionManagement // 启用 @Transactional
public class TxConfig {

    @Bean
    public PlatformTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
方式2:Spring Boot 自动配置

只要引入 spring-boot-starter-jdbcspring-boot-starter-data-jpa事务自动启用,无需额外配置!


第六章:常见陷阱与解决方案

6.1 陷阱1:自调用导致事务失效

复制代码
@Service
public class OrderService {

    public void createOrder(Order order) {
        this.validate(order); // ❌ this 调用,绕过代理!
        this.save(order);
    }

    @Transactional
    public void validate(Order order) {
        // 此处事务不生效!
    }
}

解决方案

  • 重构:将 validate 移到另一个 Service;
  • 或通过 ApplicationContext 获取代理自身(不推荐);
  • 或使用 AopContext.currentProxy()(需 exposeProxy=true)。

6.2 陷阱2:异常被捕获,事务不回滚

复制代码
@Transactional
public void transfer(...) {
    try {
        accountDao.debit(...);
        accountDao.credit(...);
    } catch (Exception e) {
        log.error("转账失败", e);
        // ❌ 异常被吞掉,Spring 不知道要回滚!
    }
}

解决方案

  • 不要捕获异常,或捕获后重新抛出;
  • 或显式调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

6.3 陷阱3:Checked Exception 默认不回滚

复制代码
@Transactional
public void readFile() throws IOException {
    // 若抛出 IOException(checked exception),默认不回滚!
}

解决方案

复制代码
@Transactional(rollbackFor = Exception.class) // 回滚所有异常
// 或
@Transactional(rollbackFor = IOException.class)

📌 Spring 默认只对 RuntimeException 和 Error 回滚


6.4 陷阱4:只读事务误用

复制代码
@Transactional(readOnly = true)
public void updateUser(User user) {
    userDao.update(user); // ❌ 在只读事务中写数据!
}
  • 某些数据库(如 Oracle)会抛异常;
  • MySQL 可能静默执行,但违背设计意图。

建议:只读事务仅用于查询方法。


6.5 陷阱5:非 public 方法事务无效

复制代码
@Transactional
protected void internalMethod() { ... } // ❌ JDK 动态代理无法拦截

解决方案 :确保方法为 public


第七章:XML 配置方式(了解即可)

虽然注解是主流,但 XML 仍存在于老项目中:

复制代码
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="txManager"/>

<!-- 或使用 XML 声明式事务 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
    <tx:attributes>
        <tx:method name="transfer*" propagation="REQUIRED" rollback-for="Exception"/>
        <tx:method name="get*" read-only="true"/>
    </tx:attributes>
</tx:advice>

<aop:config>
    <aop:pointcut id="serviceMethods" expression="execution(* com.service.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>
</aop:config>

⚠️ 新项目应优先使用 @Transactional


第八章:最佳实践总结

建议 说明
事务方法保持细粒度 避免大事务(长时间持有锁)
明确指定 rollbackFor 避免 checked exception 不回滚
避免事务中远程调用 网络延迟可能导致事务超时
不要在事务中做耗时操作 如文件 IO、复杂计算
测试事务回滚场景 使用 @Rollback 注解进行单元测试
监控长事务 防止数据库连接耗尽

结语

Spring 的声明式事务是其最成功的抽象之一------它将复杂的事务管理简化为一个注解,同时保留了足够的灵活性以应对各种业务场景。

但"简单"不等于"无脑"。只有理解其背后的 传播行为、隔离级别、代理机制和常见陷阱,才能真正写出健壮、可靠的事务代码。

记住:

事务不是魔法,它是责任

每一个 @Transactional,都承载着数据一致性的承诺。

希望本文能助你在事务之路上行稳致远。

参考资料

  1. Spring Framework 官方文档:Transaction Management
  2. 《Spring 实战(第6版)》第11章
  3. Martin Fowler: Patterns of Enterprise Application Architecture
  4. MySQL 官方文档:InnoDB Transaction Model

相关推荐
qq_124987075344 分钟前
基于SpringBoot+vue的小黄蜂外卖平台(源码+论文+部署+安装)
java·开发语言·vue.js·spring boot·后端·mysql·毕业设计
i02081 小时前
Java 17 + Spring Boot 3.2.5 使用 Redis 实现“生产者–消费者”任务队列
java·spring boot·redis
万邦科技Lafite1 小时前
一键获取淘宝店铺所有商品信息,实时监控商品数据
开发语言·数据库·python·api·开放api·电商开放平台·淘宝开放平台
SUPER52661 小时前
运维hbase服务重启,导致应用查询异常 hbase:meta
运维·数据库·hbase
烤麻辣烫1 小时前
黑马程序员苍穹外卖后端概览
xml·java·数据库·spring·intellij-idea
点灯小铭1 小时前
基于单片机的智能药物盒设计与实现
数据库·单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
天天摸鱼的java工程师1 小时前
JDK 25 到底更新了什么?这篇全景式解读带你全面掌握
java·后端
毕设源码-邱学长1 小时前
【开题答辩全过程】以 个人博客网站为例,包含答辩的问题和答案
java
非鱼feiyu1 小时前
自关联数据表查询优化实践:以 Django + 递归 CTE 构建树结构为例
数据库·后端·django