首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >揭秘 Spring 事务黑科技:从底层原理到实战落地

揭秘 Spring 事务黑科技:从底层原理到实战落地

作者头像
果酱带你啃java
发布2026-04-14 12:11:54
发布2026-04-14 12:11:54
570
举报
Spring事务管理
Spring事务管理

Spring事务管理详解:从理论到实践的完美指南

深入理解事务本质、传播机制与最佳实践

在分布式系统大行其道的今天,数据一致性成为了系统设计的重中之重。Spring框架提供了强大而灵活的事务管理机制,让开发者能够从繁琐的事务管理中解放出来,专注于业务逻辑的实现。本文将深入剖析Spring事务管理的核心原理、实现方式与实战技巧。

一、事务的本质:数据一致性的守护者

想象一下,当用户在电商平台下单时,扣减库存、创建订单、扣减余额这一系列操作,如果中间某一步失败了,后果不堪设想。事务,正是解决这类问题的关键技术。

事务具有四大核心特性,通常被称为 ACID 特性:

  • 原子性(Atomicity)事务是一个不可分割的工作单位,要么全部执行成功,要么全部失败回滚,不存在部分成功的情况。
  • 一致性(Consistency)事务执行前后,数据从一个一致性状态转换到另一个一致性状态。例如转账操作,无论成功与否,双方账户总额保持不变。
  • 隔离性(Isolation)多个事务并发执行时,一个事务的执行不应影响其他事务的执行,它们之间相互隔离。
  • 持久性(Durability)一旦事务提交成功,其对数据的修改就是永久性的,即使系统发生故障也不会丢失。

在Java EE时代,开发者需要手动管理事务的开启、提交和回滚,代码冗余且容易出错。Spring框架的出现彻底改变了这一现状。

二、Spring 事务管理的核心架构

Spring 事务管理的核心在于其抽象层设计,通过一套统一的接口屏蔽了不同持久化技术的事务管理差异。理解这些核心接口,是掌握 Spring 事务实现原理的关键。

2.1 PlatformTransactionManager:事务管理器的核心接口

PlatformTransactionManager是 Spring 事务管理的核心接口,它定义了事务管理的基本操作:

代码语言:javascript
复制
public interface PlatformTransactionManager {
    // 获取事务状态
    TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;

    // 提交事务
    void commit(TransactionStatus status) throws TransactionException;

    // 回滚事务
    void rollback(TransactionStatus status) throws TransactionException;
}

Spring 为不同的持久化技术提供了具体实现:

  • DataSourceTransactionManager用于 JDBC 和 MyBatis 等基于 DataSource 的持久化技术
  • HibernateTransactionManager用于 Hibernate 框架
  • JpaTransactionManager用于 JPA 技术
  • JtaTransactionManager用于分布式事务管理

DataSourceTransactionManager为例,它通过管理Connection对象来实现事务控制,本质上是对 JDBC 事务的封装。

2.2 TransactionDefinition:事务属性定义

TransactionDefinition接口定义了事务的基本属性,包括隔离级别、传播行为、超时时间和是否只读等:

代码语言:javascript
复制
public interface TransactionDefinition {
    // 事务传播行为
    int getPropagationBehavior();

    // 事务隔离级别
    int getIsolationLevel();

    // 事务超时时间(秒)
    int getTimeout();

    // 是否为只读事务
    boolean isReadOnly();

    // 事务名称
    @Nullable
    String getName();
}
代码语言:javascript
复制

Spring 提供了DefaultTransactionDefinition作为默认实现,我们也可以根据需要自定义实现。

2.3 TransactionStatus:事务运行状态

TransactionStatus接口用于表示事务的当前运行状态,提供了事务控制的基本方法:

代码语言:javascript
复制
public interface TransactionStatus extends SavepointManager, Flushable {
    // 是否是新事务
    boolean isNewTransaction();

    // 是否有保存点
    boolean hasSavepoint();

    // 设置事务为仅回滚状态
    void setRollbackOnly();

    // 事务是否为仅回滚状态
    boolean isRollbackOnly();

    // 刷新事务
    @Override
    void flush();

    // 事务是否已完成
    boolean isCompleted();
}
代码语言:javascript
复制

通过TransactionStatus,我们可以获取事务的当前状态,并对事务进行一些控制操作,如设置回滚点、标记事务为仅回滚等。

这三个核心接口构成了 Spring 事务管理的基础架构,它们之间的协作关系如下:

  1. 开发者通过TransactionDefinition定义事务属性
  2. PlatformTransactionManager根据TransactionDefinition获取TransactionStatus
  3. 通过TransactionStatus对事务进行提交、回滚等操作

三、Spring 事务传播机制:解决事务嵌套难题

事务传播机制是 Spring 事务管理中最具特色的功能之一,它解决了多个事务方法嵌套调用时的事务如何传播的问题。Spring 定义了 7 种事务传播行为,每种行为都有其特定的适用场景。

3.1 事务传播行为详解

  1. REQUIRED(默认):如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
  2. SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。
  3. MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
  4. REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则将当前事务挂起。
  5. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将当前事务挂起。
  6. NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。
  7. NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。

3.2 事务传播行为实例分析

为了更好地理解这些传播行为,我们通过一个电商下单的场景来进行实例分析。假设有三个核心方法:

  • createOrder()创建订单
  • deductInventory()扣减库存
  • deductBalance()扣减余额

我们将通过不同的传播行为组合,观察事务的执行效果。

首先,我们定义一个订单实体类:

代码语言:javascript
复制
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Order {
    private Long id;
    private Long userId;
    private Long productId;
    private Integer quantity;
    private BigDecimal amount;
    private String status;
}
代码语言:javascript
复制

然后,创建相关的 Mapper 接口:

代码语言:javascript
复制
@Mapper
public interface OrderMapper {
    @Insert("INSERT INTO `order` (user_id, product_id, quantity, amount, status) " +
            "VALUES (#{userId}, #{productId}, #{quantity}, #{amount}, #{status})")
    @Options(useGeneratedKeys = true, keyProperty = "id")
    void insert(Order order);
}

@Mapper
public interface InventoryMapper {
    @Update("UPDATE inventory SET stock = stock - #{quantity} WHERE product_id = #{productId} AND stock >= #{quantity}")
    int deduct(@Param("productId") Long productId, @Param("quantity") Integer quantity);
}

@Mapper
public interface AccountMapper {
    @Update("UPDATE account SET balance = balance - #{amount} WHERE user_id = #{userId} AND balance >= #{amount}")
    int deduct(@Param("userId") Long userId, @Param("amount") BigDecimal amount);
}
代码语言:javascript
复制

接下来,我们实现 Service 层代码,并通过不同的传播行为组合来测试事务效果:

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private AccountService accountService;

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    /**
     * 创建订单,使用默认的REQUIRED传播行为
     */
    @Transactional
    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");

        // 查询商品单价(实际项目中应该有专门的商品服务)
        BigDecimal price = new BigDecimal("99.99");
        BigDecimal amount = price.multiply(new BigDecimal(quantity));

        // 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setQuantity(quantity);
        order.setAmount(amount);
        order.setStatus("PENDING");
        orderMapper.insert(order);
        log.info("订单创建成功,订单ID:{}", order.getId());

        // 扣减库存
        inventoryService.deductInventory(productId, quantity);

        // 扣减余额
        accountService.deductBalance(userId, amount);

        // 更新订单状态为已完成
        // 这里省略更新订单状态的代码
        log.info("订单创建完成");
    }
}

@Service
@Slf4j
public class InventoryService {

    @Autowired
    private InventoryMapper inventoryMapper;

    /**
     * 扣减库存,使用REQUIRED传播行为
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void deductInventory(Long productId, Integer quantity) {
        log.info("开始扣减库存,商品ID:{},数量:{}", productId, quantity);
        int rows = inventoryMapper.deduct(productId, quantity);
        if (rows == 0) {
            log.error("库存不足,商品ID:{},请求数量:{}", productId, quantity);
            throw new InsufficientInventoryException("库存不足");
        }
        log.info("库存扣减成功");
    }
}

@Service
@Slf4j
public class AccountService {

    @Autowired
    private AccountMapper accountMapper;

    /**
     * 扣减余额,使用REQUIRED传播行为
     */
    @Transactional(propagation = Propagation.REQUIRED)
    public void deductBalance(Long userId, BigDecimal amount) {
        log.info("开始扣减余额,用户ID:{},金额:{}", userId, amount);
        int rows = accountMapper.deduct(userId, amount);
        if (rows == 0) {
            log.error("余额不足,用户ID:{},请求金额:{}", userId, amount);
            throw new InsufficientBalanceException("余额不足");
        }
        log.info("余额扣减成功");
    }
}

在上面的代码中,三个方法都使用了REQUIRED传播行为。当我们调用createOrder()方法时:

  1. createOrder()方法会创建一个新的事务
  2. 调用deductInventory()方法时,由于当前已存在事务,会加入到该事务中
  3. 调用deductBalance()方法时,同样会加入到已存在的事务中
  4. 如果三个方法都执行成功,则事务提交
  5. 如果任何一个方法抛出异常,则整个事务回滚

接下来,我们修改deductBalance()方法的传播行为为REQUIRES_NEW

代码语言:javascript
复制
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductBalance(Long userId, BigDecimal amount) {
    // 方法实现不变
}

此时,当我们调用createOrder()方法时:

  1. createOrder()方法创建一个新的事务(事务 A)
  2. deductInventory()方法加入事务 A
  3. 调用deductBalance()方法时,会暂停事务 A,创建一个新的事务(事务 B)
  4. 如果事务 B 执行成功,则立即提交
  5. 恢复事务 A 的执行,如果后续操作出现异常,事务 A 会回滚,但事务 B 的提交结果不会受到影响

这种传播行为适用于那些即使主事务失败也需要确保执行成功的操作,例如日志记录。

四、Spring 事务隔离级别:平衡并发与一致性

在多用户并发访问数据库的场景下,事务隔离级别决定了一个事务对数据的修改如何被其他事务可见。Spring 定义了 5 种事务隔离级别,对应数据库的隔离级别。

4.1 事务隔离级别详解

  1. DEFAULT:使用数据库默认的隔离级别(MySQL 默认是 REPEATABLE_READ,Oracle 默认是 READ_COMMITTED)
  2. READ_UNCOMMITTED:最低的隔离级别,一个事务可以读取另一个未提交事务的数据。可能导致脏读、不可重复读和幻读。
  3. READ_COMMITTED:一个事务只能读取另一个已提交事务的数据,可以避免脏读,但可能导致不可重复读和幻读。
  4. REPEATABLE_READ:确保在同一个事务中多次读取同一数据时,结果是一致的。可以避免脏读和不可重复读,但可能导致幻读。
  5. SERIALIZABLE:最高的隔离级别,所有事务依次执行,完全避免了脏读、不可重复读和幻读,但性能最低。

4.2 并发问题实例分析

为了更好地理解不同隔离级别如何解决并发问题,我们通过具体实例来分析脏读、不可重复读和幻读。

4.2.1 脏读(Dirty Read)

脏读是指一个事务读取了另一个未提交事务修改的数据。当未提交的事务回滚时,已读取的数据就变成了 "脏数据"。

示例场景

  • 用户 A 的余额为 1000 元
  • 事务 1:用户 A 转账 500 元给用户 B,先将 A 的余额改为 500 元,但未提交
  • 事务 2:读取用户 A 的余额,得到 500 元
  • 事务 1:发生错误,回滚事务,用户 A 的余额恢复为 1000 元
  • 事务 2:基于读取到的 500 元进行后续操作,导致数据不一致

解决方案:使用 READ_COMMITTED 或更高的隔离级别。

4.2.2 不可重复读(Non-repeatable Read)

不可重复读是指在同一个事务中,多次读取同一数据时,得到的结果不一致。这通常是因为另一个事务修改并提交了该数据。

示例场景

  • 事务 1:读取用户 A 的余额为 1000 元
  • 事务 2:修改用户 A 的余额为 800 元并提交
  • 事务 1:再次读取用户 A 的余额,得到 800 元,与第一次读取的结果不一致

解决方案:使用 REPEATABLE_READ 或 SERIALIZABLE 隔离级别。

4.2.3 幻读(Phantom Read)

幻读是指在同一个事务中,多次执行相同的查询时,得到的结果集不一致。这通常是因为另一个事务插入或删除了数据并提交。

示例场景

  • 事务 1:查询库存数量大于 100 的商品,得到 3 条记录
  • 事务 2:插入一条库存数量为 200 的商品记录并提交
  • 事务 1:再次执行相同的查询,得到 4 条记录,出现了 "幻影" 记录

解决方案:使用 SERIALIZABLE 隔离级别。

4.3 隔离级别实战配置

在 Spring 中,我们可以通过@Transactional注解的isolation属性来设置事务隔离级别:

代码语言:javascript
复制
@Service
@Slf4j
public class ProductService {

    @Autowired
    private ProductMapper productMapper;

    /**
     * 查询商品信息,使用READ_COMMITTED隔离级别
     */
    @Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
    public Product getProductById(Long id) {
        log.info("查询商品信息,商品ID:{}", id);
        return productMapper.selectById(id);
    }

    /**
     * 更新商品库存,使用REPEATABLE_READ隔离级别
     */
    @Transactional(isolation = Isolation.REPEATABLE_READ)
    public void updateStock(Long productId, Integer quantity) {
        log.info("更新商品库存,商品ID:{},数量:{}", productId, quantity);
        // 先查询当前库存
        Product product = productMapper.selectById(productId);
        if (product == null) {
            throw new ProductNotFoundException("商品不存在");
        }

        // 检查库存是否充足
        if (product.getStock() < quantity) {
            throw new InsufficientInventoryException("库存不足");
        }

        // 更新库存
        productMapper.updateStock(productId, product.getStock() - quantity);
    }
}
代码语言:javascript
复制

需要注意的是,隔离级别越高,数据一致性越好,但并发性能也会随之降低。在实际应用中,我们需要根据业务需求和性能要求,选择合适的隔离级别。

五、Spring 事务实现方式:编程式与声明式

Spring 提供了两种事务管理方式:编程式事务管理和声明式事务管理。编程式事务管理需要手动编写代码来控制事务,而声明式事务管理则通过注解或 XML 配置来实现,更加简洁灵活。

5.1 编程式事务管理

编程式事务管理通过PlatformTransactionManagerTransactionTemplate来实现,它的优点是可以精确控制事务的边界,但缺点是代码侵入性强,与业务逻辑混杂在一起。

5.1.1 使用 PlatformTransactionManager
代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    @Autowired
    private TransactionDefinition transactionDefinition;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InventoryMapper inventoryMapper;

    @Autowired
    private AccountMapper accountMapper;

    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");

        // 获取事务状态
        TransactionStatus status = transactionManager.getTransaction(transactionDefinition);

        try {
            // 查询商品单价
            BigDecimal price = new BigDecimal("99.99");
            BigDecimal amount = price.multiply(new BigDecimal(quantity));

            // 创建订单
            Order order = new Order();
            order.setUserId(userId);
            order.setProductId(productId);
            order.setQuantity(quantity);
            order.setAmount(amount);
            order.setStatus("PENDING");
            orderMapper.insert(order);
            log.info("订单创建成功,订单ID:{}", order.getId());

            // 扣减库存
            int inventoryRows = inventoryMapper.deduct(productId, quantity);
            if (inventoryRows == 0) {
                throw new InsufficientInventoryException("库存不足");
            }
            log.info("库存扣减成功");

            // 扣减余额
            int accountRows = accountMapper.deduct(userId, amount);
            if (accountRows == 0) {
                throw new InsufficientBalanceException("余额不足");
            }
            log.info("余额扣减成功");

            // 提交事务
            transactionManager.commit(status);
            log.info("订单创建完成,事务已提交");
        } catch (Exception e) {
            // 回滚事务
            transactionManager.rollback(status);
            log.error("创建订单失败,事务已回滚", e);
            throw e;
        }
    }
}
代码语言:javascript
复制

5.1.2 使用 TransactionTemplate

TransactionTemplate是对PlatformTransactionManager的封装,使用模板方法模式简化了编程式事务管理:

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InventoryMapper inventoryMapper;

    @Autowired
    private AccountMapper accountMapper;

    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");

        transactionTemplate.execute(status -> {
            try {
                // 查询商品单价
                BigDecimal price = new BigDecimal("99.99");
                BigDecimal amount = price.multiply(new BigDecimal(quantity));

                // 创建订单
                Order order = new Order();
                order.setUserId(userId);
                order.setProductId(productId);
                order.setQuantity(quantity);
                order.setAmount(amount);
                order.setStatus("PENDING");
                orderMapper.insert(order);
                log.info("订单创建成功,订单ID:{}", order.getId());

                // 扣减库存
                int inventoryRows = inventoryMapper.deduct(productId, quantity);
                if (inventoryRows == 0) {
                    throw new InsufficientInventoryException("库存不足");
                }
                log.info("库存扣减成功");

                // 扣减余额
                int accountRows = accountMapper.deduct(userId, amount);
                if (accountRows == 0) {
                    throw new InsufficientBalanceException("余额不足");
                }
                log.info("余额扣减成功");

                log.info("订单创建完成,事务将自动提交");
                return true;
            } catch (Exception e) {
                status.setRollbackOnly();
                log.error("创建订单失败,事务将回滚", e);
                throw e;
            }
        });
    }
}
代码语言:javascript
复制

5.2 声明式事务管理

声明式事务管理是 Spring 推荐的事务管理方式,它通过 AOP 技术实现,将事务管理与业务逻辑分离,代码侵入性低,配置灵活。

5.2.1 基于 @Transactional 注解

这是最常用的声明式事务管理方式,通过在方法或类上添加@Transactional注解来指定事务属性:

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private AccountService accountService;

    /**
     * 创建订单,使用声明式事务管理
     */
    @Transactional(
        propagation = Propagation.REQUIRED,
        isolation = Isolation.REPEATABLE_READ,
        timeout = 30,
        rollbackFor = {Exception.class}
    )
    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");

        // 查询商品单价
        BigDecimal price = new BigDecimal("99.99");
        BigDecimal amount = price.multiply(new BigDecimal(quantity));

        // 创建订单
        Order order = new Order();
        order.setUserId(userId);
        order.setProductId(productId);
        order.setQuantity(quantity);
        order.setAmount(amount);
        order.setStatus("PENDING");
        orderMapper.insert(order);
        log.info("订单创建成功,订单ID:{}", order.getId());

        // 扣减库存
        inventoryService.deductInventory(productId, quantity);

        // 扣减余额
        accountService.deductBalance(userId, amount);

        log.info("订单创建完成");
    }
}
代码语言:javascript
复制

@Transactional注解的主要属性:

  • propagation事务传播行为,默认 REQUIRED
  • isolation事务隔离级别,默认 DEFAULT
  • timeout事务超时时间(秒),默认 - 1(不超时)
  • readOnly是否为只读事务,默认 false
  • rollbackFor指定哪些异常会触发事务回滚
  • noRollbackFor指定哪些异常不会触发事务回滚
5.2.2 基于 XML 配置

除了注解方式,Spring 还支持通过 XML 配置来实现声明式事务管理:

代码语言:javascript
复制
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置事务通知 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="create*" propagation="REQUIRED" isolation="REPEATABLE_READ" timeout="30"/>
        <tx:method name="update*" propagation="REQUIRED"/>
        <tx:method name="delete*" propagation="REQUIRED"/>
        <tx:method name="get*" propagation="SUPPORTS" read-only="true"/>
        <tx:method name="query*" propagation="SUPPORTS" read-only="true"/>
    </tx:attributes>
</tx:advice>

<!-- 配置AOP切入点 -->
<aop:config>
    <aop:pointcut id="servicePointcut" expression="execution(* com.example.service.*Service.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="servicePointcut"/>
</aop:config>
代码语言:javascript
复制

这种方式的优点是可以集中管理事务属性,不需要修改 Java 代码,但缺点是配置相对繁琐,且事务属性与代码分离,可读性较差。

六、Spring 事务底层实现原理:AOP 与动态代理

Spring 事务管理的底层实现依赖于 AOP(面向切面编程)和动态代理技术。理解这些技术原理,有助于我们更好地使用 Spring 事务,并排查可能出现的问题。

6.1 AOP 与事务切面

Spring 事务管理本质上是一个 AOP 切面,它在目标方法执行前后插入事务管理的逻辑。具体来说,就是在目标方法执行前开启事务,在目标方法执行后根据执行结果决定提交或回滚事务。

Spring 事务切面的核心是TransactionInterceptor,它实现了MethodInterceptor接口,负责拦截目标方法的调用,并在拦截过程中实现事务管理逻辑。

代码语言:javascript
复制
public class TransactionInterceptor extends TransactionAspectSupport implements MethodInterceptor, Serializable {

    @Override
    @Nullable
    public Object invoke(MethodInvocation invocation) throws Throwable {
        // 获取目标类
        Class<?> targetClass = (invocation.getThis() != null ? AopUtils.getTargetClass(invocation.getThis()) : null);

        // 执行事务增强逻辑
        return invokeWithinTransaction(invocation.getMethod(), targetClass, invocation::proceed);
    }
}
代码语言:javascript
复制

invokeWithinTransaction方法是事务管理的核心实现,它的主要流程如下:

  1. 获取事务属性(@Transactional注解配置的属性)
  2. 确定事务管理器
  3. 开启事务
  4. 执行目标方法
  5. 根据目标方法的执行结果(正常返回或抛出异常)决定提交或回滚事务

6.2 动态代理的两种实现

Spring 事务切面通过动态代理技术应用到目标对象上。Spring 支持两种动态代理方式:JDK 动态代理和 CGLIB 动态代理。

6.2.1 JDK 动态代理

JDK 动态代理是基于接口的代理方式,它要求目标类必须实现一个或多个接口。Spring 会为目标类创建一个实现相同接口的代理类,代理类在调用目标方法时会先执行事务切面逻辑。

代码语言:javascript
复制
// 目标接口
public interface OrderService {
    void createOrder(Long userId, Long productId, Integer quantity);
}

// 目标类
@Service
public class OrderServiceImpl implements OrderService {
    @Override
    @Transactional
    public void createOrder(Long userId, Long productId, Integer quantity) {
        // 业务逻辑实现
    }
}

// JDK动态代理创建的代理类(简化版)
public class OrderServiceProxy implements OrderService {
    private OrderService target;
    private TransactionInterceptor interceptor;

    public OrderServiceProxy(OrderService target, TransactionInterceptor interceptor) {
        this.target = target;
        this.interceptor = interceptor;
    }

    @Override
    public void createOrder(Long userId, Long productId, Integer quantity) {
        // 创建方法调用对象
        MethodInvocation invocation = new MethodInvocation() {
            @Override
            public Method getMethod() {
                try {
                    return OrderService.class.getMethod("createOrder", Long.class, Long.class, Integer.class);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public Object[] getArguments() {
                return new Object[]{userId, productId, quantity};
            }

            @Override
            public Object proceed() throws Throwable {
                // 调用目标方法
                return target.createOrder(userId, productId, quantity);
            }

            // 其他方法实现省略
        };

        // 执行事务拦截器逻辑
        try {
            interceptor.invoke(invocation);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}
代码语言:javascript
复制

6.2.2 CGLIB 动态代理

CGLIB(Code Generation Library)是一个基于 ASM 的字节码生成库,它可以在运行时动态生成目标类的子类,并在子类中重写目标方法,实现代理逻辑。CGLIB 不要求目标类实现接口。

代码语言:javascript
复制
// 目标类(不实现接口)
@Service
public class OrderService {
    @Transactional
    public void createOrder(Long userId, Long productId, Integer quantity) {
        // 业务逻辑实现
    }
}

// CGLIB动态代理创建的代理类(简化版)
public class OrderService$$EnhancerByCGLIB$$ extends OrderService {
    private TransactionInterceptor interceptor;

    @Override
    public void createOrder(Long userId, Long productId, Integer quantity) {
        // 创建方法调用对象
        MethodInvocation invocation = new MethodInvocation() {
            @Override
            public Method getMethod() {
                try {
                    return OrderService.class.getMethod("createOrder", Long.class, Long.class, Integer.class);
                } catch (NoSuchMethodException e) {
                    throw new RuntimeException(e);
                }
            }

            @Override
            public Object[] getArguments() {
                return new Object[]{userId, productId, quantity};
            }

            @Override
            public Object proceed() throws Throwable {
                // 调用父类(目标类)的方法
                return super.createOrder(userId, productId, quantity);
            }

            // 其他方法实现省略
        };

        // 执行事务拦截器逻辑
        try {
            interceptor.invoke(invocation);
        } catch (Throwable e) {
            throw new RuntimeException(e);
        }
    }
}
代码语言:javascript
复制

Spring 默认的代理策略是:如果目标类实现了接口,则使用 JDK 动态代理;否则,使用 CGLIB 动态代理。我们也可以通过配置强制使用 CGLIB 代理。

6.3 事务同步管理器:ThreadLocal 的妙用

Spring 事务管理中,一个关键的组件是TransactionSynchronizationManager,它使用ThreadLocal来存储当前线程的事务状态,确保在多线程环境下事务的隔离性。

代码语言:javascript
复制
public abstract class TransactionSynchronizationManager {
    // 存储当前线程的事务资源(如Connection)
    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");

    // 存储当前线程的事务同步器
    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
            new NamedThreadLocal<>("Transaction synchronizations");

    // 存储当前线程的事务名称
    private static final ThreadLocal<String> currentTransactionName =
            new NamedThreadLocal<>("Current transaction name");

    // 存储当前线程的事务是否为只读
    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
            new NamedThreadLocal<>("Current transaction read-only status");

    // 存储当前线程的事务隔离级别
    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
            new NamedThreadLocal<>("Current transaction isolation level");

    // 存储当前线程是否存在活跃事务
    private static final ThreadLocal<Boolean> actualTransactionActive =
            new NamedThreadLocal<>("Actual transaction active");

    // 方法省略...
}
代码语言:javascript
复制

DataSourceTransactionManager开启事务时,它会从数据源获取一个Connection对象,并将其绑定到TransactionSynchronizationManagerThreadLocal变量中:

代码语言:javascript
复制
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager
        implements ResourceTransactionManager, InitializingBean {

    @Override
    protected Object doGetTransaction() {
        DataSourceTransactionObject txObject = new DataSourceTransactionObject();
        txObject.setSavepointAllowed(isNestedTransactionAllowed());
        ConnectionHolder conHolder =
                (ConnectionHolder) TransactionSynchronizationManager.getResource(obtainDataSource());
        txObject.setConnectionHolder(conHolder, false);
        return txObject;
    }

    @Override
    protected void doBegin(Object transaction, TransactionDefinition definition) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) transaction;
        Connection con = null;

        try {
            if (!txObject.hasConnectionHolder() ||
                    txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                Connection newCon = obtainDataSource().getConnection();
                if (logger.isDebugEnabled()) {
                    logger.debug("Acquired Connection [" + newCon + "] for JDBC transaction");
                }
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }

            txObject.getConnectionHolder().setSynchronizedWithTransaction(true);
            con = txObject.getConnectionHolder().getConnection();

            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);

            // 设置自动提交为false
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                if (logger.isDebugEnabled()) {
                    logger.debug("Switching JDBC Connection [" + con + "] to manual commit");
                }
                con.setAutoCommit(false);
            }

            prepareTransactionalConnection(con, definition);
            txObject.getConnectionHolder().setTransactionActive(true);

            int timeout = determineTimeout(definition);
            if (timeout != TransactionDefinition.TIMEOUT_DEFAULT) {
                txObject.getConnectionHolder().setTimeoutInSeconds(timeout);
            }

            // 将连接绑定到当前线程
            if (txObject.isNewConnectionHolder()) {
                TransactionSynchronizationManager.bindResource(obtainDataSource(), txObject.getConnectionHolder());
            }
        } catch (Throwable ex) {
            if (txObject.isNewConnectionHolder()) {
                DataSourceUtils.releaseConnection(con, obtainDataSource());
                txObject.setConnectionHolder(null, false);
            }
            throw new CannotCreateTransactionException("Could not open JDBC Connection for transaction", ex);
        }
    }

    // 其他方法省略...
}
代码语言:javascript
复制

在同一个事务中,后续的数据库操作会从TransactionSynchronizationManager中获取已绑定的Connection对象,从而保证使用的是同一个数据库连接,实现事务的 ACID 特性。

七、Spring 事务实战陷阱与解决方案

尽管 Spring 事务管理非常强大,但在实际使用中,仍然会遇到各种问题。本节将介绍一些常见的事务陷阱,并提供相应的解决方案。

7.1 自调用导致事务失效

自调用是指在同一个类中,一个方法调用另一个方法。由于 Spring 事务是基于动态代理实现的,自调用时不会经过代理对象,导致事务注解失效。

问题代码示例

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    // 非事务方法调用事务方法
    public void placeOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始处理订单");
        // 自调用,事务注解失效
        createOrder(userId, productId, quantity);
        log.info("订单处理完成");
    }

    @Transactional
    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");
        // 业务逻辑实现
        // ...
    }
}
代码语言:javascript
复制

解决方案

  1. 将方法拆分到不同的类中:
代码语言:javascript
复制
代码语言:javascript
复制
@Service
@Slf4j
public class OrderProcessService {

    @Autowired
    private OrderService orderService;

    public void placeOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始处理订单");
        orderService.createOrder(userId, productId, quantity);
        log.info("订单处理完成");
    }
}

@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");
        // 业务逻辑实现
        // ...
    }
}
代码语言:javascript
复制

通过 AopContext 获取当前代理对象:

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public void placeOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始处理订单");
        // 通过AopContext获取代理对象,再调用事务方法
        OrderService proxy = (OrderService) AopContext.currentProxy();
        proxy.createOrder(userId, productId, quantity);
        log.info("订单处理完成");
    }

    @Transactional
    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");
        // 业务逻辑实现
        // ...
    }
}

使用这种方式需要在 Spring 配置中启用暴露代理:

代码语言:javascript
复制
@Configuration
@EnableAspectJAutoProxy(exposeProxy = true)
public class AppConfig {
    // 配置内容
}
代码语言:javascript
复制

7.2 异常被捕获导致事务不回滚

默认情况下,Spring 事务只对未被捕获的RuntimeExceptionError进行回滚。如果异常被捕获而没有重新抛出,事务将不会回滚。

问题代码示例

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Autowired
    private InventoryService inventoryService;

    @Transactional
    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");

        try {
            // 创建订单
            Order order = new Order();
            // 设置订单属性...
            orderMapper.insert(order);
            log.info("订单创建成功,订单ID:{}", order.getId());

            // 扣减库存,可能抛出异常
            inventoryService.deductInventory(productId, quantity);
        } catch (Exception e) {
            log.error("创建订单失败", e);
            // 异常被捕获但未重新抛出,事务不会回滚
        }
    }
}
代码语言:javascript
复制

解决方案

  1. 不捕获异常,让异常向上抛出:
代码语言:javascript
复制
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
    log.info("开始创建订单");

    // 创建订单
    Order order = new Order();
    // 设置订单属性...
    orderMapper.insert(order);
    log.info("订单创建成功,订单ID:{}", order.getId());

    // 扣减库存,可能抛出异常
    inventoryService.deductInventory(productId, quantity);
}
代码语言:javascript
复制

  1. 捕获异常后重新抛出:
代码语言:javascript
复制
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
    log.info("开始创建订单");

    try {
        // 创建订单
        Order order = new Order();
        // 设置订单属性...
        orderMapper.insert(order);
        log.info("订单创建成功,订单ID:{}", order.getId());

        // 扣减库存,可能抛出异常
        inventoryService.deductInventory(productId, quantity);
    } catch (Exception e) {
        log.error("创建订单失败", e);
        // 重新抛出异常,触发事务回滚
        throw new RuntimeException("创建订单失败", e);
    }
}
代码语言:javascript
复制

  1. 手动设置事务回滚:
代码语言:javascript
复制
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
    log.info("开始创建订单");

    try {
        // 创建订单
        Order order = new Order();
        // 设置订单属性...
        orderMapper.insert(order);
        log.info("订单创建成功,订单ID:{}", order.getId());

        // 扣减库存,可能抛出异常
        inventoryService.deductInventory(productId, quantity);
    } catch (Exception e) {
        log.error("创建订单失败", e);
        // 手动设置事务回滚
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}
代码语言:javascript
复制

7.3 错误的异常类型导致事务不回滚

默认情况下,Spring 事务只对RuntimeExceptionError类型的异常进行回滚,对于受检异常(如IOExceptionSQLException等)不会回滚。

问题代码示例

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    @Transactional
    public void createOrder(Long userId, Long productId, Integer quantity) throws Exception {
        log.info("开始创建订单");

        // 创建订单
        Order order = new Order();
        // 设置订单属性...
        orderMapper.insert(order);
        log.info("订单创建成功,订单ID:{}", order.getId());

        // 模拟抛出受检异常
        throw new Exception("创建订单失败");
    }
}

解决方案

通过@Transactional注解的rollbackFor属性指定需要回滚的异常类型:

代码语言:javascript
复制
@Transactional(rollbackFor = Exception.class)
public void createOrder(Long userId, Long productId, Integer quantity) throws Exception {
    log.info("开始创建订单");

    // 创建订单
    Order order = new Order();
    // 设置订单属性...
    orderMapper.insert(order);
    log.info("订单创建成功,订单ID:{}", order.getId());

    // 模拟抛出受检异常
    throw new Exception("创建订单失败");
}
代码语言:javascript
复制

7.4 非 public 方法的事务注解失效

Spring 事务注解@Transactional只能应用在 public 方法上,对非 public 方法(如 private、protected、default)的注解会被忽略,导致事务失效。

问题代码示例

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public void placeOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始处理订单");
        createOrder(userId, productId, quantity);
        log.info("订单处理完成");
    }

    // 非public方法,事务注解失效
    @Transactional
    private void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");
        // 业务逻辑实现
        // ...
    }
}
代码语言:javascript
复制

解决方案

将方法改为 public 访问权限:

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    public void placeOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始处理订单");
        createOrder(userId, productId, quantity);
        log.info("订单处理完成");
    }

    // 改为public方法
    @Transactional
    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");
        // 业务逻辑实现
        // ...
    }
}
代码语言:javascript
复制

7.5 多数据源导致事务失效

在使用多数据源的场景中,如果事务管理器配置不正确,可能导致事务失效。

问题代码示例

代码语言:javascript
复制
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.first")
    public DataSource firstDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.second")
    public DataSource secondDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    public DataSourceTransactionManager transactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}
代码语言:javascript
复制

在上面的配置中,Spring 会自动选择一个数据源作为主数据源,但当我们操作其他数据源时,事务管理可能失效。

解决方案

为每个数据源配置单独的事务管理器,并在@Transactional注解中指定使用的事务管理器:

代码语言:javascript
复制
@Configuration
public class DataSourceConfig {

    @Bean
    @ConfigurationProperties("spring.datasource.first")
    public DataSource firstDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    @ConfigurationProperties("spring.datasource.second")
    public DataSource secondDataSource() {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean
    public DataSourceTransactionManager firstTransactionManager() {
        return new DataSourceTransactionManager(firstDataSource());
    }

    @Bean
    public DataSourceTransactionManager secondTransactionManager() {
        return new DataSourceTransactionManager(secondDataSource());
    }
}
代码语言:javascript
复制

在 Service 中指定事务管理器:

代码语言:javascript
复制
@Service
@Slf4j
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;

    // 指定使用第一个事务管理器
    @Transactional(transactionManager = "firstTransactionManager")
    public void createOrder(Long userId, Long productId, Integer quantity) {
        log.info("开始创建订单");
        // 业务逻辑实现
        // ...
    }
}

@Service
@Slf4j
public class InventoryService {

    @Autowired
    private InventoryMapper inventoryMapper;

    // 指定使用第二个事务管理器
    @Transactional(transactionManager = "secondTransactionManager")
    public void deductInventory(Long productId, Integer quantity) {
        log.info("开始扣减库存");
        // 业务逻辑实现
        // ...
    }
}
代码语言:javascript
复制

如果需要跨多个数据源的事务管理,则需要使用分布式事务解决方案,如 Seata、TCC 等。

八、Spring 事务最佳实践

掌握 Spring 事务的最佳实践,能够帮助我们写出更健壮、更高效的代码。以下是一些经过实践检验的最佳实践建议。

8.1 合理设置事务边界

事务边界应尽可能小,只包含必要的数据库操作,避免将耗时操作(如远程调用、IO 操作)包含在事务中,以减少锁竞争,提高并发性能。

推荐做法

代码语言:javascript
复制
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
    // 只包含数据库操作
    createOrderRecord(userId, productId, quantity);
    deductInventory(productId, quantity);
    deductBalance(userId, calculateAmount(productId, quantity));
}

// 非事务方法,包含耗时操作
public void processOrder(Long userId, Long productId, Integer quantity) {
    // 远程调用获取商品信息
    Product product = productService.getProduct(productId);

    // 验证库存(非事务性查询)
    if (!inventoryService.checkInventory(productId, quantity)) {
        throw new InsufficientInventoryException("库存不足");
    }

    // 验证余额(非事务性查询)
    if (!accountService.checkBalance(userId, calculateAmount(productId, quantity))) {
        throw new InsufficientBalanceException("余额不足");
    }

    // 调用事务方法
    createOrder(userId, productId, quantity);

    // 发送消息通知(异步操作)
    messageService.sendOrderCreatedMessage(userId, productId, quantity);

    // 记录操作日志(异步操作)
    logService.recordOperationLog(userId, "CREATE_ORDER", "创建订单成功");
}
代码语言:javascript
复制

8.2 正确设置事务传播行为

根据业务需求选择合适的事务传播行为,避免滥用REQUIRES_NEW等传播行为,因为它们可能导致事务嵌套和资源竞争。

推荐做法

  • 大多数情况下,使用默认的REQUIRED传播行为即可
  • 对于不需要事务的查询操作,使用SUPPORTS传播行为
  • 对于必须在事务中执行的操作,使用MANDATORY传播行为
  • 只有在确实需要独立事务的场景下,才使用REQUIRES_NEW传播行为

8.3 合理设置事务隔离级别

根据业务对数据一致性和并发性能的要求,选择合适的事务隔离级别。

推荐做法

  • 大多数情况下,使用数据库默认的隔离级别即可
  • 对于读多写少的场景,可以考虑使用READ_COMMITTED隔离级别
  • 对于数据一致性要求高的场景,可以使用REPEATABLE_READ隔离级别
  • 尽量避免使用SERIALIZABLE隔离级别,除非确实需要最高级别的数据一致性

8.4 显式指定回滚异常类型

虽然 Spring 有默认的事务回滚规则,但为了代码的可读性和明确性,建议显式指定回滚异常类型。

推荐做法

代码语言:javascript
复制
// 显式指定回滚异常类型
@Transactional(rollbackFor = {BusinessException.class, DataAccessException.class})
public void createOrder(Long userId, Long productId, Integer quantity) {
    // 业务逻辑实现
}
代码语言:javascript
复制

8.5 为只读事务设置 readOnly 属性

对于只涉及查询操作的事务,设置readOnly = true可以提示数据库优化事务处理,提高性能。

推荐做法

代码语言:javascript
复制
// 只读事务
@Transactional(readOnly = true)
public List<Order> queryUserOrders(Long userId) {
    return orderMapper.selectByUserId(userId);
}

8.6 合理设置事务超时时间

为事务设置合理的超时时间,避免长时间运行的事务占用数据库资源,导致锁竞争和性能问题。

推荐做法

代码语言:javascript
复制
// 设置事务超时时间为30秒
@Transactional(timeout = 30)
public void createOrder(Long userId, Long productId, Integer quantity) {
    // 业务逻辑实现
}
代码语言:javascript
复制

8.7 避免长事务

长事务是指运行时间较长的事务,它们会长时间占用数据库连接和锁资源,导致并发性能下降。

推荐做法

  • 将长事务拆分为多个短事务
  • 使用异步处理非核心流程
  • 对于复杂业务,考虑使用最终一致性方案

8.8 正确处理事务中的异常

在事务中捕获异常时,要确保异常能够正确触发事务回滚,避免出现数据不一致的情况。

推荐做法

代码语言:javascript
复制
@Transactional
public void createOrder(Long userId, Long productId, Integer quantity) {
    try {
        // 业务逻辑实现
    } catch (BusinessException e) {
        log.error("业务异常", e);
        // 重新抛出异常,触发回滚
        throw e;
    } catch (Exception e) {
        log.error("系统异常", e);
        // 包装为运行时异常抛出,触发回滚
        throw new SystemException("系统异常", e);
    }
}

九、总结

Spring 事务管理是 Spring 框架中最核心、最常用的功能之一,它通过 AOP 和动态代理技术,为开发者提供了简单易用、灵活强大的事务管理能力。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2025-08-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 果酱带你啃java 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 2.1 PlatformTransactionManager:事务管理器的核心接口
  • 2.2 TransactionDefinition:事务属性定义
  • 2.3 TransactionStatus:事务运行状态
  • 3.1 事务传播行为详解
  • 3.2 事务传播行为实例分析
  • 四、Spring 事务隔离级别:平衡并发与一致性
    • 4.1 事务隔离级别详解
    • 4.2 并发问题实例分析
      • 4.2.1 脏读(Dirty Read)
      • 4.2.2 不可重复读(Non-repeatable Read)
      • 4.2.3 幻读(Phantom Read)
    • 4.3 隔离级别实战配置
    • 5.1 编程式事务管理
      • 5.1.1 使用 PlatformTransactionManager
      • 5.1.2 使用 TransactionTemplate
    • 5.2 声明式事务管理
      • 5.2.1 基于 @Transactional 注解
      • 5.2.2 基于 XML 配置
    • 6.1 AOP 与事务切面
    • 6.2 动态代理的两种实现
      • 6.2.1 JDK 动态代理
      • 6.2.2 CGLIB 动态代理
    • 6.3 事务同步管理器:ThreadLocal 的妙用
    • 7.1 自调用导致事务失效
    • 7.2 异常被捕获导致事务不回滚
    • 7.3 错误的异常类型导致事务不回滚
    • 7.4 非 public 方法的事务注解失效
    • 7.5 多数据源导致事务失效
    • 8.1 合理设置事务边界
    • 8.2 正确设置事务传播行为
    • 8.3 合理设置事务隔离级别
    • 8.4 显式指定回滚异常类型
    • 8.5 为只读事务设置 readOnly 属性
    • 8.6 合理设置事务超时时间
    • 8.7 避免长事务
    • 8.8 正确处理事务中的异常
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档