【Java开发】Spring 事务开发完全指南:从入门到精通

发布于:2025-06-13 ⋅ 阅读:(26) ⋅ 点赞:(0)

目录

一、为什么需要事务?从一个真实案例说起

二、Spring 事务核心概念:先搞懂这三个关键组件

2.1 三大核心接口

三、声明式事务:99% 的场景都用它(附完整代码示例)

3.1 最简入门:3 步搞定基础使用

步骤 1:添加依赖(Maven/Gradle)

步骤 2:配置事务管理器(Spring Boot 自动配置,非 Boot 需手动)

步骤 3:在 Service 方法上添加 @Transactional 注解

3.2 @Transactional 注解全参数解析

传播行为深度解析(最容易混淆的点)

四、编程式事务:适合复杂场景的精细控制

4.1 什么时候用编程式事务?

4.2 代码示例:使用 TransactionTemplate

4.3 对比声明式 vs 编程式

五、Spring Boot 事务自动配置:新手也能秒懂的原理

5.1 自动配置流程(图解)

5.2 手动配置 override(进阶)

5.3 事务属性配置(application.properties)

六、事务失效?这 8 个坑 90% 的新手都踩过

6.1 坑 1:方法修饰符不是 public

6.2 坑 2:同一类内方法调用

6.3 坑 3:未处理的检查型异常(Checked Exception)

6.4 坑 4:自动提交未关闭(@Transactional 失效的隐藏原因)

6.5 坑 5:事务管理器未正确注入

6.6 坑 6:使用了错误的事务管理器(多数据源场景)

6.7 坑 7:异常被吞掉(最隐蔽的坑)

6.8 坑 8:类未被 Spring 管理

七、与 MyBatis/JPA 整合:具体场景的最佳实践

7.1 MyBatis 场景:批量操作的事务优化

7.2 JPA 场景:只读事务优化查询性能

7.3 多数据源场景:指定事务管理器

八、分布式事务

8.1 什么是分布式事务?

8.2 三种主流方案对比

8.3 Spring 集成 Seata(最简示例)

九、事务调试与监控:必备的排查技巧

9.1 开启调试日志(关键!)

9.2 常用排查命令

9.3 生产环境监控指标

十、总结


一、为什么需要事务?从一个真实案例说起

假设你正在开发一个电商系统,用户下单时需要同时完成两个操作:

  1. 创建订单记录(插入订单表)
  2. 扣减库存(更新商品表库存字段)

如果没有事务保护,可能会出现以下情况:

  • 订单创建成功,但库存扣减失败
  • 系统突然断电,导致部分操作未持久化
  • 多用户并发下单时,库存出现负数

事务的核心价值:确保这两个操作要么全部成功,要么全部失败,就像一个 "原子操作"。

二、Spring 事务核心概念:先搞懂这三个关键组件

2.1 三大核心接口

(1)PlatformTransactionManager(事务管理器)

  • 作用:管理事务的创建、提交、回滚
  • 常见实现类:
    • DataSourceTransactionManager(用于 JDBC/MyBatis)
    • JpaTransactionManager(用于 JPA/Hibernate)
    • HibernateTransactionManager(旧版 Hibernate 专用)
  • 问题:为什么需要选择不同的实现类?

👉 因为不同持久化框架的事务操作方式不同,比如 JPA 和 MyBatis 的底层连接获取方式有差异

(2)TransactionDefinition(事务定义)

  • 作用:定义事务的属性(隔离级别、传播行为、超时时间等)
  • 核心属性:
public interface TransactionDefinition {
    int getPropagationBehavior(); // 传播行为(7种)
    int getIsolationLevel(); // 隔离级别(5种)
    int getTimeout(); // 超时时间(秒)
    boolean isReadOnly(); // 是否只读事务
}

(3)TransactionStatus(事务状态)

  • 作用:保存事务运行时的状态(是否新事务、是否已回滚等)
  • 常用方法:
status.isNewTransaction(); // 是否是新创建的事务
status.setRollbackOnly(); // 标记为仅回滚(手动回滚)

三、声明式事务:99% 的场景都用它(附完整代码示例)

3.1 最简入门:3 步搞定基础使用

步骤 1:添加依赖(Maven/Gradle)
<!-- Spring Boot场景 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId> <!-- 包含事务支持 -->
</dependency>

<!-- 非Spring Boot场景需手动添加 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>6.0.11</version>
</dependency>

步骤 2:配置事务管理器(Spring Boot 自动配置,非 Boot 需手动)
// Spring Boot自动配置,无需额外代码
// 非Spring Boot场景需在配置类中添加:
@Configuration
public class TransactionConfig {
    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

步骤 3:在 Service 方法上添加 @Transactional 注解
@Service
public class OrderService {

    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private ProductRepository productRepository;

    // 核心业务方法,添加事务注解
    @Transactional
    public void createOrder(Order order) {
        // 1. 创建订单
        orderRepository.save(order);
        
        // 2. 扣减库存(假设这里可能抛出异常)
        productRepository.decreaseStock(order.getProductId(), order.getQuantity());
    }
}

3.2 @Transactional 注解全参数解析

参数名

作用

默认值

示例

value

限定作用的事务管理器 Bean 名称

空(使用默认管理器)

@Transactional("customTransactionManager")

propagation

事务传播行为(解决多个事务方法嵌套调用的问题)

Propagation.REQUIRED

@Transactional(propagation = Propagation.REQUIRES_NEW)

isolation

事务隔离级别(解决并发事务的数据可见性问题)

Isolation.DEFAULT

@Transactional(isolation = Isolation.READ_COMMITTED)

timeout

事务超时时间(超时则自动回滚)

-1(永不超时)

@Transactional(timeout = 10)

readOnly

标记为只读事务(优化数据库性能)

false

@Transactional(readOnly = true)

rollbackFor

指定需要回滚的异常类型(可多个)

空(仅回滚 RuntimeException)

@Transactional(rollbackFor = {SQLException.class, IOException.class})

rollbackForClassName

同上,通过异常类名指定(字符串形式)

@Transactional(rollbackForClassName = "java.sql.SQLException")

noRollbackFor

指定不需要回滚的异常类型(可多个)

@Transactional(noRollbackFor = BusinessException.class)

noRollbackForClassName

同上,通过异常类名指定(字符串形式)

@Transactional(noRollbackForClassName = "com.example.BusinessException")

传播行为深度解析(最容易混淆的点)

假设存在以下调用关系:

methodA() { ... methodB() ... }

methodA和methodB都添加了 @Transactional 注解,传播行为决定了两者的事务如何关联。

传播行为

英文名称

核心逻辑

典型场景

REQUIRED

必填(默认)

如果当前有事务,加入该事务;否则创建新事务

普通业务方法

REQUIRES_NEW

需要新事务

挂起当前事务,创建新事务执行

独立于外层的子事务(如日志记录)

SUPPORTS

支持事务

如果当前有事务,加入该事务;否则以非事务方式执行

可选事务的查询方法

NOT_SUPPORTED

不支持事务

挂起当前事务,以非事务方式执行

性能优先的只读查询

MANDATORY

强制事务

必须存在当前事务,否则抛出异常

依赖外层事务的子操作

NEVER

禁止事务

必须不存在当前事务,否则抛出异常

绝对不允许事务的场景

NESTED

嵌套事务

在当前事务中创建一个保存点(仅 JDBC 支持,Hibernate 不支持)

可部分回滚的子操作

案例

外层方法A(REQUIRED)调用内层方法B(REQUIRES_NEW):

  • A先创建事务 T1
  • B挂起 T1,创建新事务 T2
  • 如果B抛出异常,T2 回滚,T1 可选择继续执行或回滚

四、编程式事务:适合复杂场景的精细控制

4.1 什么时候用编程式事务?

  • 需要在事务执行过程中动态获取事务状态
  • 需要更细粒度的事务控制(如手动设置回滚)
  • 非注解场景(如第三方框架集成)

4.2 代码示例:使用 TransactionTemplate

@Service
public class OrderService {

    @Autowired
    private TransactionTemplate transactionTemplate;
    @Autowired
    private OrderRepository orderRepository;
    @Autowired
    private ProductRepository productRepository;

    public void createOrderWithProgrammaticTransaction(Order order) {
        transactionTemplate.execute(status -> { // 传入TransactionCallback
            try {
                orderRepository.save(order);
                productRepository.decreaseStock(order.getProductId(), order.getQuantity());
                // 主动提交需返回null或正常对象,异常会触发回滚
                return null;
            } catch (Exception e) {
                // 手动标记回滚(可选,抛出异常也会回滚)
                status.setRollbackOnly(); 
                throw new RuntimeException("事务执行失败", e);
            }
        });
    }
}

4.3 对比声明式 vs 编程式

特性

声明式(@Transactional)

编程式(TransactionTemplate)

学习成本

低(简单注解)

中(需要理解 TransactionCallback)

灵活性

低(固定流程)

高(可控制事务每个阶段)

代码侵入性

低(无额外代码)

中(需要编写回调逻辑)

适用场景

90% 的普通业务场景

复杂控制、非注解场景

五、Spring Boot 事务自动配置:新手也能秒懂的原理

5.1 自动配置流程(图解)

5.2 手动配置 override(进阶)

如果自动配置的事务管理器不满足需求(如多数据源),需手动配置:

@Configuration
public class MultiDataSourceConfig {

    @Bean(name = "primaryTransactionManager")
    public DataSourceTransactionManager primaryTransactionManager(
        @Qualifier("primaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean(name = "secondaryTransactionManager")
    public DataSourceTransactionManager secondaryTransactionManager(
        @Qualifier("secondaryDataSource") DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

5.3 事务属性配置(application.properties)

# 全局事务隔离级别(可被方法注解覆盖)
spring.jpa.properties.hibernate.transaction.isolation=READ_COMMITTED

# 配置事务超时时间(单位:秒,对所有事务生效)
spring.transaction.default-timeout=30

六、事务失效?这 8 个坑 90% 的新手都踩过

6.1 坑 1:方法修饰符不是 public

@Service
public class UserService {
    // 错误:protected方法不会被AOP代理
    @Transactional
    protected void updateUser() { ... }

    // 错误:private方法无法被代理
    @Transactional
    private void deleteUser() { ... }

    // 正确:public方法
    @Transactional
    public void saveUser() { ... }
}

6.2 坑 2:同一类内方法调用

@Service
public class OrderService {

    // 外部调用有效
    @Override
    @Transactional
    public void createOrder() {
        saveOrder(); // 内部调用,事务失效!
    }

    // 内部方法无注解
    private void saveOrder() { ... }

    // 正确做法:通过@Autowired注入自身代理
    @Autowired
    private OrderService selfProxy;

    @Override
    public void createOrder() {
        selfProxy.saveOrder(); // 通过代理调用,事务生效
    }

    @Transactional
    private void saveOrder() { ... }
}

6.3 坑 3:未处理的检查型异常(Checked Exception)

@Transactional
public void updateStock() throws IOException {
    // 抛出检查型异常(非RuntimeException)
    throw new IOException("库存更新失败"); 
    // 👉 默认不会回滚,因为@Transactional只回滚RuntimeException
}

// 修正:显式指定回滚异常
@Transactional(rollbackFor = IOException.class)
public void updateStock() throws IOException {
    throw new IOException("库存更新失败"); // 现在会回滚
}

6.4 坑 4:自动提交未关闭(@Transactional 失效的隐藏原因)

如果使用 MyBatis,需确保SqlSession关闭自动提交:

// MyBatis配置类(重要!)
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    // 关闭自动提交,让Spring事务控制提交
    factory.getObject().getConfiguration().setAutoCommit(false); 
    return factory;
}

6.5 坑 5:事务管理器未正确注入

@Service
public class UserService {
    // 错误:未注入事务管理器,直接使用会报空指针
    private TransactionTemplate transactionTemplate;

    // 正确:通过@Autowired注入
    @Autowired
    private TransactionTemplate transactionTemplate;
}

6.6 坑 6:使用了错误的事务管理器(多数据源场景)

// 错误:未指定事务管理器,默认使用第一个创建的
@Transactional
public void updatePrimaryDb() { ... }

// 正确:指定事务管理器Bean名称
@Transactional("primaryTransactionManager")
public void updatePrimaryDb() { ... }

6.7 坑 7:异常被吞掉(最隐蔽的坑)

@Transactional
public void processOrder() {
    try {
        // 可能抛出异常的代码
        orderRepository.save(order);
        // 故意不处理异常
    } catch (Exception e) {
        // 仅打印日志,未重新抛出异常
        log.error("处理订单失败", e);
    }
    // 👉 事务不会回滚,因为异常没有传播出去
}

// 修正:重新抛出异常或手动设置回滚
@Transactional
public void processOrder() {
    try {
        // ...
    } catch (Exception e) {
        log.error("处理订单失败", e);
        // 手动标记回滚(即使不抛异常)
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); 
    }
}

6.8 坑 8:类未被 Spring 管理

// 错误:直接new对象,不受Spring事务管理
public class ManualService {
    @Autowired
    private UserRepository userRepository;

    // 事务无效,因为该类未被@Service标记
    public void manualTransaction() {
        userRepository.save(new User());
    }
}

// 正确:添加@Service注解
@Service
public class ManualService {
    // 事务生效
}

七、与 MyBatis/JPA 整合:具体场景的最佳实践

7.1 MyBatis 场景:批量操作的事务优化

@Service
public class BatchService {

    @Autowired
    private UserMapper userMapper;

    // 错误:每次插入都开启新事务(性能差)
    public void batchInsertOld(List<User> users) {
        for (User user : users) {
            @Transactional // 注解在循环内,无效!
            userMapper.insert(user);
        }
    }

    // 正确:批量操作放在一个事务内
    @Transactional
    public void batchInsertNew(List<User> users) {
        userMapper.batchInsert(users); // 一次性插入(需Mapper支持批量操作)
    }
}

7.2 JPA 场景:只读事务优化查询性能

@Service
public class QueryService {

    @Autowired
    private UserRepository userRepository;

    // 标记为只读事务,数据库可优化锁机制
    @Transactional(readOnly = true) 
    public List<User> getUsersByAge(int age) {
        return userRepository.findByAge(age);
    }
}

7.3 多数据源场景:指定事务管理器

@Service
public class MultiDbService {

    // 操作主库事务
    @Transactional("primaryTransactionManager") 
    public void updatePrimaryDb() {
        primaryRepository.update(...);
    }

    // 操作从库事务
    @Transactional("secondaryTransactionManager") 
    public void updateSecondaryDb() {
        secondaryRepository.update(...);
    }
}

八、分布式事务

8.1 什么是分布式事务?

当操作涉及多个数据库实例(如微服务架构中的订单库和库存库),传统本地事务失效,需要分布式事务解决方案。

8.2 三种主流方案对比

方案

核心原理

一致性

性能

适用场景

XA 协议

两阶段提交(2PC)

强一致

金融等高一致性场景

TCC 模式

Try-Confirm-Cancel 三阶段

最终一致

分布式电商、支付系统

Saga 模式

补偿事务(正向操作 + 反向补偿)

最终一致

长事务、柔性事务场景

8.3 Spring 集成 Seata(最简示例)

  1. 添加依赖:
    <dependency>
        <groupId>io.seata</groupId>
        <artifactId>seata-spring-boot-starter</artifactId>
        <version>1.7.0</version>
    </dependency>
  2. 服务层添加全局事务注解:
    @GlobalTransactional // 分布式事务注解
    public void createOrder(Order order) {
        orderService.create(order);         // 订单库操作
        inventoryService.decreaseStock();  // 库存库操作
    }

九、事务调试与监控:必备的排查技巧

9.1 开启调试日志(关键!)

在application.properties中添加:

# 开启Spring事务调试日志
logging.level.org.springframework.transaction=DEBUG

# 查看具体的SQL执行日志(MyBatis场景)
logging.level.com.example.mapper=DEBUG

9.2 常用排查命令

  1. 查看当前事务状态:
    TransactionStatus status = TransactionAspectSupport.currentTransactionStatus();
    boolean isNewTransaction = status.isNewTransaction(); // 是否新事务
    boolean isRollbackOnly = status.isRollbackOnly(); // 是否已标记回滚
  2. 查看事务管理器配置:
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    // 输出事务管理器类型
    System.out.println(transactionManager.getClass().getName()); 

9.3 生产环境监控指标

建议监控以下指标(通过 Micrometer 或 Prometheus):

  • spring.transaction.active:当前活动事务数
  • spring.transaction.completed:已完成事务数(成功 + 失败)
  • transaction.rollback.rate:事务回滚率

十、总结

  1. 能用声明式就不用编程式:注解优先,减少代码复杂度
  2. 事务范围尽可能小
    1. 错误:在事务中包含网络调用、文件操作
    2. 正确:仅包裹必要的数据库操作
  3. 明确指定回滚异常
    @Transactional(rollbackFor = Exception.class) // 回滚所有异常

    ​​​​​​​

  4. 只读事务标记readOnly=true:提升数据库查询性能
  5. 设置合理的超时时间:避免长事务占用数据库连接
  6. 多数据源场景指定事务管理器:通过@Transactional("xxxTransactionManager")
  7. 内部方法调用使用代理对象:通过@Autowired注入自身解决事务失效
  8. 检查方法修饰符:必须是public方法才能被 AOP 代理
  9. 关闭 MyBatis 自动提交:确保 Spring 完全控制事务边界
  10. 开启调试日志:遇到事务问题先看日志,再断点调试
​​​​​​​​​​​​​​

只要按照这个指南一步步实践,即使是完全不懂事务的新手,也能在 Spring 开发中熟练运用事务,确保数据的一致性和完整性。记住:事务的核心是 "要么全做,要么全不做",所有的配置和代码都是为了实现这个目标。遇到问题不要慌,先看日志、再查注解参数、最后断点调试,99% 的问题都能解决!