SpringBoot(07):事务管理——@Transactional 你真的用对了吗?

SpringBoot(07):事务管理------@Transactional 你真的用对了吗?

线上出了个事故:用户下单扣了钱,但订单状态没更新。排查半天,发现是 @Transactional 注解加在了 private 方法上,根本没生效。类似的事故我见过不少------同一个类里方法互相调用、异常类型没配对、传播行为选错------每一项都能让事务白加。@Transactional 就一个注解,看着简单,用错了线上就出事。

问题:事务失效的几种常见写法

看几段有问题的代码:

typescript 复制代码
@Service
public class OrderService {

    // 问题1:同一个类里方法互相调用
    public void createOrder(Order order) {
        orderMapper.insert(order);
        this.updateInventory(order);  // 事务不生效!
    }

    @Transactional
    public void updateInventory(Order order) {
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
    }
}
typescript 复制代码
@Service
public class PaymentService {

    // 问题2:注解加在 private 方法上
    @Transactional
    private void processPayment(Payment payment) {
        accountMapper.deduct(payment.getAmount());
        paymentMapper.updateStatus(payment.getId(), "PAID");
    }

    public void pay(Payment payment) {
        this.processPayment(payment);
    }
}
kotlin 复制代码
@Service
public class TransferService {

    // 问题3:异常被 catch 了,没抛出去
    @Transactional
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        try {
            accountMapper.deduct(fromId, amount);
            accountMapper.add(toId, amount);
        } catch (Exception e) {
            log.error("转账失败", e);
            // 没抛出去,Spring 以为方法正常返回,直接提交事务
        }
    }
}

这三段代码的共同点: @Transactional 加了,但事务没生效。要理解为什么,得从 Spring 事务管理的底层原理说起。

Spring 事务管理架构

Spring 事务管理的核心组件:

  • @Transactional --- 注解标记,声明这个方法需要事务
  • TransactionInterceptor --- 事务拦截器,AOP 代理在方法调用前后执行它
  • PlatformTransactionManager --- 事务管理器接口,负责 begin/commit/rollback
  • DataSourceTransactionManager --- JDBC/MyBatis 场景下的实现
  • JpaTransactionManager --- JPA 场景下的实现

调用链路:Controller → AOP Proxy → TransactionInterceptor → Target Method。AOP 代理在调用真实方法之前先开事务,方法正常返回就提交,抛异常就回滚。

这也是为什么 self-invocation(同一个类里 this.xxx())事务不生效------绕过了 AOP 代理,直接调用目标方法,TransactionInterceptor 根本没机会执行。

@Transactional 执行流程

  1. 客户端调用带 @Transactional 的方法
  2. AOP 代理拦截请求,转入 TransactionInterceptor
  3. TransactionInterceptor 从 TransactionManager 获取事务状态
  4. TransactionManager 从 DataSource 获取 Connection,关闭自动提交(setAutoCommit(false))
  5. 执行目标方法,方法内的所有 SQL 操作共享同一个 Connection
  6. 方法正常返回 → commit
  7. 方法抛出异常 → 检查是否需要回滚(根据 rollbackFor 配置)→ rollback

@Transactional 七大属性详解

ini 复制代码
@Transactional(
    propagation = Propagation.REQUIRED,       // 传播行为
    isolation = Isolation.DEFAULT,             // 隔离级别
    timeout = -1,                              // 超时时间(秒)
    readOnly = false,                          // 是否只读
    rollbackFor = RuntimeException.class,      // 哪些异常回滚
    noRollbackFor = BusinessException.class,   // 哪些异常不回滚
    transactionManager = "transactionManager"  // 事务管理器
)
public void someMethod() { }

1. propagation --- 传播行为

传播行为决定:当前方法遇到一个已经存在的事务时,怎么做。一共 7 种,常用的 3 种标了星号:

传播行为 说明 使用场景
REQUIRED * 有事务就加入,没有就新建 默认值,90% 的场景用这个
REQUIRES_NEW * 不管有没有,都新建一个,外面的事务挂起 日志记录、审计,不想因为日志失败影响主业务
NESTED * 有事务就在里面嵌套一个保存点,没有就新建 子操作可以单独回滚,但不影响外层事务
SUPPORTS 有事务就加入,没有就非事务执行 查询方法,不强求事务
NOT_SUPPORTED 有事务就挂起,非事务执行 不需要事务的操作
MANDATORY 必须在事务中调用,否则抛异常 强制要求调用方开事务
NEVER 必须不在事务中调用,否则抛异常 不允许在事务中执行的操作
实战:REQUIRED vs REQUIRES_NEW vs NESTED
java 复制代码
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private InventoryMapper inventoryMapper;
    @Autowired
    private AuditLogService auditLogService;

    // REQUIRED:默认传播行为
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        orderMapper.insert(order);                    // SQL 1
        inventoryMapper.deduct(order.getProductId(), order.getQuantity()); // SQL 2
        // SQL 1 和 SQL 2 在同一个事务里
        // 任何一步失败,全部回滚
    }
}
java 复制代码
@Service
public class AuditLogService {

    @Autowired
    private AuditLogMapper auditLogMapper;

    // REQUIRES_NEW:不管外层有没有事务,自己开一个新的
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String action, Long orderId) {
        AuditLog log = new AuditLog();
        log.setAction(action);
        log.setOrderId(orderId);
        auditLogMapper.insert(log);
        // 即使外层事务回滚,这条日志也会提交
    }
}
java 复制代码
@Service
public class OrderService {

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private InventoryMapper inventoryMapper;
    @Autowired
    private GiftService giftService;

    // NESTED:嵌套事务,内部失败不影响外层
    @Transactional(propagation = Propagation.REQUIRED)
    public void createOrder(Order order) {
        orderMapper.insert(order);
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());

        try {
            // 赠品逻辑独立出来,赠品失败不影响主订单
            giftService.sendGift(order);
        } catch (Exception e) {
            log.warn("赠品发放失败,不影响主订单", e);
        }
    }
}

@Service
public class GiftService {

    @Transactional(propagation = Propagation.NESTED)
    public void sendGift(Order order) {
        giftMapper.insert(new Gift(order.getUserId(), order.getId()));
        // 如果这里抛异常,只回滚到保存点
        // 外层的 orderMapper.insert 和 inventoryMapper.deduct 不受影响
    }
}

三种传播行为的区别:

场景 REQUIRED REQUIRES_NEW NESTED
外层有事务 加入外层事务 新开独立事务 嵌套保存点
内层回滚 整个事务回滚 只回滚内层事务 只回滚到保存点
外层回滚 内层也回滚 内层不受影响 内层也回滚
连接数 共享一个 Connection 需要两个 Connection 共享一个 Connection

2. isolation --- 隔离级别

MySQL 默认隔离级别是 REPEATABLE_READ(可重复读),Spring Boot 直接沿用数据库的设置(Isolation.DEFAULT)。一般不需要改。

隔离级别 脏读 不可重复读 幻读 说明
READ_UNCOMMITTED 可能 可能 可能 最低级别,基本不用
READ_COMMITTED 不会 可能 可能 Oracle 默认级别
REPEATABLE_READ 不会 不会 可能 MySQL 默认级别
SERIALIZABLE 不会 不会 不会 最高级别,性能差
typescript 复制代码
// 大多数情况用 DEFAULT 就行,让数据库自己决定
@Transactional(isolation = Isolation.DEFAULT)
public void createOrder(Order order) {
    orderMapper.insert(order);
}

// 特殊场景:报表查询,允许脏读换取性能
@Transactional(isolation = Isolation.READ_UNCOMMITTED, readOnly = true)
public List<Order> reportOrders() {
    return orderMapper.selectAll();
}

3. readOnly --- 只读事务

ini 复制代码
@Transactional(readOnly = true)
public OrderDTO getOrder(Long orderId) {
    Order order = orderMapper.selectById(orderId);
    User user = userMapper.selectById(order.getUserId());
    return new OrderDTO(order, user);
}

readOnly = true 的效果:

  • JPA/Hibernate 不会做脏检查(flush),性能有提升
  • MySQL 执行 START TRANSACTION READ ONLY,InnoDB 可以做优化
  • JDBC 的 Connection.setReadOnly(true) 会路由到从库(如果配了读写分离)

查询方法加上 readOnly = true 是个好习惯。

4. rollbackFor --- 回滚条件

这是踩坑最多的属性。

java 复制代码
// Spring 默认行为:只回滚 RuntimeException 和 Error
// 不回滚 checked Exception(比如 IOException)

// 问题代码:
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount)
        throws InsufficientBalanceException {  // 自定义 checked Exception
    accountMapper.deduct(fromId, amount);
    accountMapper.add(toId, amount);
    // 如果 deduct 之后抛 InsufficientBalanceException
    // Spring 不会回滚!钱扣了但没加过去
}

// 正确写法:指定回滚的异常类型
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount)
        throws InsufficientBalanceException {
    accountMapper.deduct(fromId, amount);
    accountMapper.add(toId, amount);
}

一条规则:永远加 rollbackFor = Exception.class 让所有异常都回滚,不要依赖 Spring 的默认行为。

5. noRollbackFor --- 不回滚条件

arduino 复制代码
// 业务异常不想回滚(比如余额不足,但前面的操作要保留)
@Transactional(rollbackFor = Exception.class, noRollbackFor = BusinessException.class)
public void process(Order order) {
    orderMapper.insert(order);
    if (order.getAmount().compareTo(BigDecimal.ZERO) < 0) {
        throw new BusinessException("金额不能为负");
        // 抛 BusinessException 不回滚,orderMapper.insert 的记录保留
    }
}

这个属性用得少,但在某些业务场景下有用:前面的操作要保留,只有特定异常不触发回滚。

6. timeout --- 超时时间

java 复制代码
@Transactional(timeout = 5)  // 5 秒超时
public void batchInsert(List<Order> orders) {
    for (Order order : orders) {
        orderMapper.insert(order);  // 如果 5 秒内没执行完,自动回滚
    }
}

默认 -1,不超时。批量操作、耗时计算等场景可以加上,防止长事务锁表。

7. transactionManager --- 指定事务管理器

多数据源环境下必须指定:

typescript 复制代码
@Transactional(transactionManager = "orderTransactionManager")
public void createOrder(Order order) {
    orderMapper.insert(order);
}

@Transactional(transactionManager = "userTransactionManager")
public void updateUser(User user) {
    userMapper.update(user);
}

不指定的话,Spring 用 @Primary 标记的那个事务管理器。

事务失效的 7 种场景

场景 1:同一个类里方法调用(self-invocation)

typescript 复制代码
@Service
public class OrderService {

    public void createOrder(Order order) {
        orderMapper.insert(order);
        this.updateInventory(order);  // ← 绕过了 AOP 代理
    }

    @Transactional
    public void updateInventory(Order order) {
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
    }
}

this.updateInventory() 直接调用目标方法,不经过 AOP 代理,TransactionInterceptor 没机会拦截。

解决办法

scss 复制代码
// 方法1:把事务方法拆到另一个 Service
@Service
public class InventoryService {
    @Transactional
    public void updateInventory(Order order) {
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
    }
}

@Service
public class OrderService {
    @Autowired
    private InventoryService inventoryService;

    public void createOrder(Order order) {
        orderMapper.insert(order);
        inventoryService.updateInventory(order);  // 通过代理调用
    }
}

// 方法2:注入自己(不太优雅)
@Service
public class OrderService {
    @Autowired
    private OrderService self;

    public void createOrder(Order order) {
        orderMapper.insert(order);
        self.updateInventory(order);  // 通过代理调用
    }

    @Transactional
    public void updateInventory(Order order) {
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
    }
}

// 方法3:用 AopContext 获取代理对象(需要开启 exposeProxy)
@EnableAspectJAutoProxy(exposeProxy = true)
@SpringBootApplication
public class Application { }

@Service
public class OrderService {
    public void createOrder(Order order) {
        orderMapper.insert(order);
        ((OrderService) AopContext.currentProxy()).updateInventory(order);
    }

    @Transactional
    public void updateInventory(Order order) {
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
    }
}

三种方案,推荐方案 1:把事务逻辑拆到独立的 Service,干净且好测试。

场景 2:方法不是 public

typescript 复制代码
@Service
public class OrderService {

    @Transactional
    private void process(Order order) {  // private!Spring AOP 不拦截非 public 方法
        orderMapper.insert(order);
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
    }

    @Transactional
    protected void handle(Order order) {  // protected 也不行
        orderMapper.insert(order);
    }
}

Spring AOP 默认只拦截 public 方法。非 public 方法上的 @Transactional 相当于白加。

解决办法:把方法改成 public。

场景 3:异常被吞了

kotlin 复制代码
@Service
public class TransferService {

    @Transactional(rollbackFor = Exception.class)
    public void transfer(Long fromId, Long toId, BigDecimal amount) {
        try {
            accountMapper.deduct(fromId, amount);
            accountMapper.add(toId, amount);
        } catch (Exception e) {
            log.error("转账失败", e);
            // 异常没抛出去,Spring 以为正常返回,提交事务
        }
    }
}

解决办法:catch 完了要 throw,或者手动标记回滚。

csharp 复制代码
// 方法1:抛出去
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    try {
        accountMapper.deduct(fromId, amount);
        accountMapper.add(toId, amount);
    } catch (Exception e) {
        log.error("转账失败", e);
        throw e;
    }
}

// 方法2:手动标记回滚
@Transactional(rollbackFor = Exception.class)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
    try {
        accountMapper.deduct(fromId, amount);
        accountMapper.add(toId, amount);
    } catch (Exception e) {
        log.error("转账失败", e);
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
    }
}

场景 4:异常类型不对

java 复制代码
@Service
public class OrderService {

    @Transactional  // 默认只回滚 RuntimeException
    public void createOrder(Order order) throws IOException {
        orderMapper.insert(order);
        fileService.exportOrder(order);  // 抛 IOException(checked Exception)
        // 事务不会回滚!orderMapper.insert 的记录保留
    }
}

解决办法 :永远加 rollbackFor = Exception.class

java 复制代码
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) throws IOException {
    orderMapper.insert(order);
    fileService.exportOrder(order);
}

场景 5:数据库引擎不支持事务

MySQL 的 MyISAM 引擎不支持事务。如果表用的是 MyISAM,@Transactional 加了也白加。

sql 复制代码
-- 检查表引擎
SHOW TABLE STATUS LIKE 'orders';

-- 如果 Engine 是 MyISAM,改成 InnoDB
ALTER TABLE orders ENGINE = InnoDB;

Spring Boot 2.x + MySQL 5.7+ 默认用 InnoDB,一般不会有这个问题。但老项目迁移的时候要注意。

场景 6:传播行为用错了

scss 复制代码
@Service
public class OrderService {

    @Transactional(propagation = Propagation.NOT_SUPPORTED)
    public void createOrder(Order order) {
        orderMapper.insert(order);
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
        // NOT_SUPPORTED:以非事务方式执行,出错了不回滚
    }

    @Transactional(propagation = Propagation.SUPPORTS)
    public void createOrder2(Order order) {
        orderMapper.insert(order);
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
        // SUPPORTS:如果没有外层事务,也是非事务执行
    }
}

写操作必须用 REQUIRED(默认值)或 REQUIRES_NEW。NOT_SUPPORTED 和 SUPPORTS 用在查询方法上。

场景 7:Bean 没被 Spring 管理

typescript 复制代码
// 没加 @Service 或 @Component
public class OrderService {

    @Transactional
    public void createOrder(Order order) {
        orderMapper.insert(order);
        // 不是 Spring Bean,@Transactional 完全无效
    }
}

或者通过 new OrderService() 创建的对象,也不归 Spring 管。

解决办法 :确保类上有 @Service / @Component,且通过 @Autowired 注入使用。

源码分析:@Transactional 的底层实现

1. 事务拦截器:TransactionInterceptor

scala 复制代码
// org.springframework.transaction.interceptor.TransactionInterceptor
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, () -> {
            return invocation.proceed();  // 执行目标方法
        });
    }
}

TransactionInterceptor 实现了 MethodInterceptor 接口,AOP 代理在调用目标方法时,会先执行 invoke()。invoke() 里调用父类 TransactionAspectSupport 的 invokeWithinTransaction()。

2. 核心逻辑:TransactionAspectSupport

java 复制代码
// org.springframework.transaction.interceptor.TransactionAspectSupport
public abstract class TransactionAspectSupport {

    @Nullable
    protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
            InvocationCallback invocation) throws Throwable {

        // 1. 获取事务属性(@Transactional 的配置)
        TransactionAttributeSource tas = getTransactionAttributeSource();
        final TransactionAttribute txAttr = (tas != null ?
                tas.getTransactionAttribute(method, targetClass) : null);

        // 2. 获取事务管理器
        final PlatformTransactionManager tm = determineTransactionManager(txAttr);

        // 3. 创建事务
        final TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, methodIdentification);

        Object retVal;
        try {
            // 4. 执行目标方法
            retVal = invocation.proceedWithInvocation();
        } catch (Throwable ex) {
            // 5. 异常处理:根据 rollbackFor 判断回滚还是提交
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        } finally {
            cleanupTransactionInfo(txInfo);
        }

        // 6. 正常返回:提交事务
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }

    private void completeTransactionAfterThrowing(TransactionInfo txInfo, Throwable ex) {
        if (txInfo.transactionAttribute.rollbackOn(ex)) {
            // 回滚
            txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
        } else {
            // 不回滚,提交
            txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
        }
    }
}

这段源码把事务的完整生命周期写得很清楚:

  1. 解析 @Transactional 注解的属性
  2. 获取对应的事务管理器
  3. 开启事务(createTransactionIfNecessary)
  4. 执行目标方法
  5. 异常时判断是否回滚(rollbackOn)
  6. 正常时提交(commitTransactionAfterReturning)

3. 事务管理器:AbstractPlatformTransactionManager

java 复制代码
// org.springframework.transaction.support.AbstractPlatformTransactionManager
public abstract class AbstractPlatformTransactionManager implements PlatformTransactionManager {

    @Override
    public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
            throws TransactionException {

        // 获取当前事务对象(DataSource 场景下就是 Connection)
        Object transaction = doGetTransaction();

        // 检查是否已存在事务(处理传播行为)
        if (isExistingTransaction(transaction)) {
            // 已有事务,根据传播行为决定:加入、挂起、抛异常
            return handleExistingTransaction(definition, transaction, debugEnabled);
        }

        // 没有事务,根据传播行为决定是否新建
        if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
            throw new IllegalTransactionStateException("当前没有事务,但传播行为是 MANDATORY");
        } else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
                definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
            // 挂起(如果需要),开新事务
            SuspendedResourcesHolder suspendedResources = suspend(null);
            try {
                return startTransaction(definition, transaction, debugEnabled, suspendedResources);
            } catch (RuntimeException ex) {
                resume(null, suspendedResources);
                throw ex;
            }
        } else {
            // 不需要事务的传播行为
            return prepareTransactionStatus(definition, null, true, debugEnabled, null);
        }
    }

    private TransactionStatus startTransaction(TransactionDefinition definition, Object transaction,
            boolean debugEnabled, @Nullable SuspendedResourcesHolder suspendedResources) {

        boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
        DefaultTransactionStatus status = newTransactionStatus(
                definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);

        // 子类实现:DataSourceTransactionManager 里就是 connection.setAutoCommit(false)
        doBegin(transaction, definition);
        prepareSynchronization(status, definition);
        return status;
    }
}

doBegin() 是关键。DataSourceTransactionManager 的实现:

scss 复制代码
// org.springframework.jdbc.datasource.DataSourceTransactionManager
public class DataSourceTransactionManager extends AbstractPlatformTransactionManager {

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

        try {
            if (!txObject.hasConnectionHolder() ||
                    txObject.getConnectionHolder().isSynchronizedWithTransaction()) {
                // 从 DataSource 获取 Connection
                Connection newCon = obtainDataSource().getConnection();
                txObject.setConnectionHolder(new ConnectionHolder(newCon), true);
            }

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

            // 设置隔离级别和只读
            Integer previousIsolationLevel = DataSourceUtils.prepareConnectionForTransaction(con, definition);
            txObject.setPreviousIsolationLevel(previousIsolationLevel);
            txObject.setReadOnly(definition.isReadOnly());

            // 关键:关闭自动提交
            if (con.getAutoCommit()) {
                txObject.setMustRestoreAutoCommit(true);
                con.setAutoCommit(false);  // ← 事务从这里开始
            }

            // 如果是只读事务,执行 SET TRANSACTION READ ONLY
            prepareTransactionalConnection(con, definition);
            txObject.getConnectionHolder().setTransactionActive(true);

        } catch (Throwable ex) {
            DataSourceUtils.releaseConnection(con, obtainDataSource());
            throw new CannotCreateTransactionException("无法打开 JDBC Connection 进行事务", ex);
        }
    }

    @Override
    protected void doCommit(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        try {
            con.commit();  // ← 提交
        } catch (SQLException ex) {
            throw new TransactionSystemException("提交 JDBC 事务失败", ex);
        }
    }

    @Override
    protected void doRollback(DefaultTransactionStatus status) {
        DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
        Connection con = txObject.getConnectionHolder().getConnection();
        try {
            con.rollback();  // ← 回滚
        } catch (SQLException ex) {
            throw new TransactionSystemException("回滚 JDBC 事务失败", ex);
        }
    }
}

源码看下来,Spring 事务管理的本质就三步:

  1. con.setAutoCommit(false) --- 开事务
  2. 执行业务方法
  3. con.commit()con.rollback() --- 提交或回滚

Spring 帮你把这三步用 AOP 封装好了,你只需要加个注解。但如果你绕过了 AOP 代理(self-invocation)、或者异常没传到 TransactionInterceptor(catch 吞了),这三步就执行不对。

编程式事务 vs 声明式事务

声明式事务就是前面讲的 @Transactional,通过 AOP 实现。编程式事务是直接在代码里控制事务边界。

编程式事务:TransactionTemplate

scss 复制代码
@Service
public class OrderService {

    @Autowired
    private TransactionTemplate transactionTemplate;

    @Autowired
    private OrderMapper orderMapper;
    @Autowired
    private InventoryMapper inventoryMapper;

    public void createOrder(Order order) {
        transactionTemplate.execute(status -> {
            try {
                orderMapper.insert(order);
                inventoryMapper.deduct(order.getProductId(), order.getQuantity());
            } catch (Exception e) {
                status.setRollbackOnly();
                throw e;
            }
            return null;
        });
    }

    // 有返回值的写法
    public Order createAndReturn(Order order) {
        return transactionTemplate.execute(status -> {
            orderMapper.insert(order);
            inventoryMapper.deduct(order.getProductId(), order.getQuantity());
            return order;
        });
    }
}

编程式事务:PlatformTransactionManager

scss 复制代码
@Service
public class OrderService {

    @Autowired
    private PlatformTransactionManager transactionManager;

    public void createOrder(Order order) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        def.setIsolationLevel(TransactionDefinition.ISOLATION_DEFAULT);
        def.setTimeout(10);

        TransactionStatus status = transactionManager.getTransaction(def);
        try {
            orderMapper.insert(order);
            inventoryMapper.deduct(order.getProductId(), order.getQuantity());
            transactionManager.commit(status);
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

两种方式对比

对比项 声明式(@Transactional) 编程式(TransactionTemplate)
代码侵入 低,一个注解 高,需要手动写事务逻辑
灵活性 方法级别控制 代码块级别控制
可读性 好,一眼就看到 差,事务逻辑和业务逻辑混在一起
self-invocation 问题 没有(不走 AOP)
适用场景 大部分 CRUD 操作 需要精确控制事务边界的场景

一般用声明式就够了。编程式事务适合这种场景:一个方法里,只有部分代码需要在事务里执行。

scss 复制代码
public void processOrder(Order order) {
    // 1. 校验(不需要事务)
    validateOrder(order);

    // 2. 入库(需要事务)
    transactionTemplate.execute(status -> {
        orderMapper.insert(order);
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
        return null;
    });

    // 3. 发通知(不需要事务,失败也不回滚)
    notificationService.sendOrderCreated(order);
}

最佳实践

1. @Transactional 加在 Service 层

less 复制代码
// Controller 层不加事务
@RestController
@RequestMapping("/order")
public class OrderController {
    @Autowired
    private OrderService orderService;

    @PostMapping("/create")
    public Result create(@RequestBody OrderRequest request) {
        return Result.success(orderService.createOrder(request.toOrder()));
    }
}

// Service 层加事务
@Service
public class OrderService {
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Order order) {
        orderMapper.insert(order);
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
    }
}

事务加在 Service 层,Controller 层只做参数校验和结果封装。这样不管有多少个 Controller 调用同一个 Service 方法,事务都能生效。

2. 永远加 rollbackFor = Exception.class

less 复制代码
// 不好
@Transactional
public void transfer() { }

// 好
@Transactional(rollbackFor = Exception.class)
public void transfer() { }

3. 查询方法加 readOnly = true

typescript 复制代码
@Transactional(readOnly = true)
public OrderDTO getOrder(Long orderId) {
    return new OrderDTO(orderMapper.selectById(orderId));
}

4. 事务方法尽量短

scss 复制代码
// 不好:事务里做了太多非数据库操作
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
    orderMapper.insert(order);           // DB 操作
    inventoryMapper.deduct(...);          // DB 操作
    emailService.sendConfirm(order);      // 发邮件,可能耗时几十秒
    smsService.sendNotify(order);         // 发短信,可能耗时几秒
    // 整个方法执行期间事务都开着,锁住数据库连接
}

// 好:事务只包数据库操作
public void createOrder(Order order) {
    doCreateOrder(order);                 // 事务内
    emailService.sendConfirm(order);      // 事务外
    smsService.sendNotify(order);         // 事务外
}

@Transactional(rollbackFor = Exception.class)
public void doCreateOrder(Order order) {
    orderMapper.insert(order);
    inventoryMapper.deduct(order.getProductId(), order.getQuantity());
}

长事务会长时间持有数据库连接和行锁,影响并发性能。事务方法里只放数据库操作,RPC 调用、消息发送、文件操作放到事务外面。

5. 避免嵌套事务陷阱

java 复制代码
@Service
public class OrderService {

    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Order order) {
        orderMapper.insert(order);
        try {
            inventoryService.deduct(order);
        } catch (Exception e) {
            log.warn("扣库存失败", e);
            // 这里 catch 了,但 inventoryService.deduct 的事务可能已经标记了 rollbackOnly
            // 外层事务 commit 时会抛 UnexpectedRollbackException
        }
    }
}

@Service
public class InventoryService {

    @Transactional(rollbackFor = Exception.class)  // 默认 REQUIRED,加入外层事务
    public void deduct(Order order) {
        inventoryMapper.deduct(order.getProductId(), order.getQuantity());
        // 如果这里抛异常,事务状态被标记为 rollbackOnly
    }
}

REQUIRED 传播行为下,内层方法加入外层事务。内层抛异常时,事务状态被标记为 rollbackOnly。外层即使 catch 了异常,commit 时也会抛 UnexpectedRollbackException。

解决办法:内层方法用 NESTED 传播行为,或者把内层逻辑改成非事务方式。

6. 多数据源要指定 transactionManager

typescript 复制代码
@Transactional(transactionManager = "orderTransactionManager", rollbackFor = Exception.class)
public void createOrder(Order order) {
    orderMapper.insert(order);
}

总结

知识点 要点
核心原理 AOP 代理 + TransactionInterceptor + PlatformTransactionManager
执行流程 拦截方法 → setAutoCommit(false) → 执行方法 → commit/rollback
传播行为 REQUIRED(默认)、REQUIRES_NEW(独立事务)、NESTED(嵌套保存点)
隔离级别 DEFAULT 即可,让数据库决定
readOnly 查询方法加上,有性能优化和读写分离路由效果
rollbackFor 永远加 Exception.class
事务失效 self-invocation、非 public、异常被吞、异常类型不对、引擎不支持、传播行为错误、非 Spring Bean
编程式事务 TransactionTemplate,适合精确控制事务边界
最佳实践 加在 Service 层、方法尽量短、非 DB 操作放事务外

@Transactional 就一个注解,背后是 AOP 代理、事务管理器、Connection 管理一整套机制。搞清楚原理,线上事务不生效的问题才好定位。

相关推荐
shepherd1111 小时前
吞吐量提升 10 倍:高并发大批量数据处理任务的架构演进与性能调优
java·后端·架构
java小白小1 小时前
SpringBoot(05):Spring Data JPA——用面向对象的方式操作数据库
后端
juejin9981 小时前
Claude Code Lab-2(上):自然语言查库助手
后端
java小白小2 小时前
SpringBoot(06):多数据源配置——一个项目连多个库怎么做
后端
程序员cxuan3 小时前
Codex 会把磁盘给烧了?完整复盘来了!
人工智能·后端·程序员
ClouGence3 小时前
Oracle 数据同步为什么会出现数据不一致?长事务是常被忽略的原因
数据库·后端·oracle
快乐肚皮4 小时前
深入理解Loop Engineering
前端·后端
小兔崽子去哪了4 小时前
Vue3 + Pinia 集成 IGV.js 实现 BAM 文件在线浏览
javascript·vue.js·后端
孟陬4 小时前
Claude Code 巧思 `Ctrl+S` 暂存键
前端·后端