面试必备:Spring Boot事务管理完整回答指南

76 阅读15分钟

"Spring Boot的事务管理在项目中用得非常多。主要使用声明式事务(@Transactional注解),它基于Spring AOP实现。我会从基本使用、核心原理、失效场景、实战优化四个方面来分享我的经验。"


一、基本使用(日常开发)

1.1 最简单的用法

@Service
public class OrderService {
    
    @Autowired
    private OrderMapper orderMapper;
    
    @Autowired
    private ProductMapper productMapper;
    
    /**
     * 创建订单 - 最基础的事务使用
     */
    @Transactional
    public void createOrder(Order order) {
        // 1. 插入订单
        orderMapper.insert(order);
        
        // 2. 扣减库存
        productMapper.decreaseStock(order.getProductId(), order.getQuantity());
        
        // 如果任何一步出错,整个事务回滚
    }
}

说明:

  • @Transactional 标注在方法上,该方法内的所有数据库操作在一个事务中
  • 如果方法正常执行完成 → 提交事务
  • 如果抛出RuntimeException或Error → 回滚事务
  • 如果抛出受检异常(Checked Exception)→ 不会回滚(这是个坑)

1.2 事务属性配置

@Transactional(
    // 1. 传播行为:当前方法被另一个事务方法调用时的行为
    propagation = Propagation.REQUIRED,
    
    // 2. 隔离级别:解决并发问题
    isolation = Isolation.READ_COMMITTED,
    
    // 3. 超时时间:防止长事务(秒)
    timeout = 30,
    
    // 4. 只读事务:优化性能
    readOnly = false,
    
    // 5. 回滚规则:哪些异常触发回滚
    rollbackFor = Exception.class,
    
    // 6. 不回滚规则:哪些异常不回滚
    noRollbackFor = IllegalArgumentException.class
)
public void complexTransaction() {
    // 业务逻辑
}

1.3 常用场景示例

场景1:订单创建(多表操作)

@Transactional(rollbackFor = Exception.class)
public Long createOrder(OrderDTO orderDTO) {
    // 1. 创建订单主表
    Order order = new Order();
    order.setUserId(orderDTO.getUserId());
    order.setTotalAmount(orderDTO.getTotalAmount());
    order.setStatus(OrderStatus.UNPAID);
    orderMapper.insert(order);
    
    // 2. 创建订单明细
    for (OrderItemDTO item : orderDTO.getItems()) {
        OrderItem orderItem = new OrderItem();
        orderItem.setOrderId(order.getId());
        orderItem.setProductId(item.getProductId());
        orderItem.setQuantity(item.getQuantity());
        orderItemMapper.insert(orderItem);
        
        // 3. 扣减库存
        int rows = productMapper.decreaseStock(
            item.getProductId(), 
            item.getQuantity()
        );
        if (rows == 0) {
            throw new BusinessException("库存不足");
        }
    }
    
    // 4. 记录操作日志
    orderLogMapper.insert(new OrderLog(order.getId(), "订单创建"));
    
    return order.getId();
}

场景2:用户注册(关联数据创建)

@Transactional(rollbackFor = Exception.class)
public void registerUser(UserRegisterDTO dto) {
    // 1. 创建用户
    User user = new User();
    user.setUsername(dto.getUsername());
    user.setPassword(passwordEncoder.encode(dto.getPassword()));
    userMapper.insert(user);
    
    // 2. 创建用户扩展信息
    UserProfile profile = new UserProfile();
    profile.setUserId(user.getId());
    profile.setNickname(dto.getNickname());
    userProfileMapper.insert(profile);
    
    // 3. 赠送新人优惠券
    CouponUser coupon = new CouponUser();
    coupon.setUserId(user.getId());
    coupon.setCouponId(NEW_USER_COUPON_ID);
    couponUserMapper.insert(coupon);
    
    // 4. 初始化钱包
    Wallet wallet = new Wallet();
    wallet.setUserId(user.getId());
    wallet.setBalance(BigDecimal.ZERO);
    walletMapper.insert(wallet);
}

二、核心原理(必须理解)

2.1 事务的本质

// 事务就是把多个数据库操作包装在一起
// Spring帮我们自动管理了以下流程:

try {
    // 开启事务
    connection.setAutoCommit(false);
    
    // 执行业务逻辑
    orderMapper.insert(order);
    productMapper.decreaseStock(productId, quantity);
    
    // 提交事务
    connection.commit();
    
} catch (Exception e) {
    // 回滚事务
    connection.rollback();
    throw e;
    
} finally {
    connection.close();
}

2.2 Spring事务实现原理(AOP代理)

// 原始类
@Service
public class OrderService {
    @Transactional
    public void createOrder() {
        // 业务逻辑
    }
}

// Spring生成的代理类(伪代码)
public class OrderService$$Proxy extends OrderService {
    
    private TransactionManager txManager;
    private OrderService target;  // 原始对象
    
    @Override
    public void createOrder() {
        TransactionInfo txInfo = null;
        try {
            // 1. 开启事务(前置增强)
            txInfo = txManager.getTransaction(txDefinition);
            
            // 2. 调用原始方法
            target.createOrder();
            
            // 3. 提交事务(后置增强)
            txManager.commit(txInfo);
            
        } catch (RuntimeException | Error e) {
            // 4. 回滚事务(异常增强)
            txManager.rollback(txInfo);
            throw e;
        }
    }
}

关键点:

  1. Spring通过AOP创建代理对象
  2. 代理对象拦截@Transactional方法
  3. 在方法执行前后增加事务管理逻辑
  4. 必须通过代理对象调用,事务才生效

2.3 事务传播行为详解

最重要的3种传播行为:

REQUIRED(默认,最常用)

@Transactional(propagation = Propagation.REQUIRED)
public void methodA() {
    // 业务逻辑A
    methodB();  // 调用B
}

@Transactional(propagation = Propagation.REQUIRED)
public void methodB() {
    // 业务逻辑B
}

// 执行结果:
// methodA有事务 → methodB加入methodA的事务(同一个事务)
// methodA无事务 → methodB创建新事务

REQUIRES_NEW(最容易出问题)

@Transactional
public void methodA() {
    orderMapper.insert(order);  // 操作1
    
    methodB();  // 调用B
    
    // 如果这里抛异常,操作1回滚,但methodB已提交,不会回滚
    throw new RuntimeException("error");
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    logMapper.insert(log);  // 操作2
}

// 执行结果:
// methodB创建新事务,立即提交
// methodA回滚时,methodB的操作不受影响

真实案例:记录操作日志

@Service
public class OrderService {
    
    @Autowired
    private LogService logService;
    
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Order order) {
        orderMapper.insert(order);
        
        // 无论订单创建成功还是失败,都要记录日志
        // 所以日志使用REQUIRES_NEW,独立事务
        logService.saveLog("创建订单", order.getId());
        
        // 如果后面业务失败,订单回滚,但日志已经保存
        productMapper.decreaseStock(order.getProductId(), order.getQuantity());
    }
}

@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void saveLog(String action, Long orderId) {
        operationLogMapper.insert(new OperationLog(action, orderId));
    }
}

NESTED(嵌套事务,用savepoint实现)

@Transactional
public void methodA() {
    orderMapper.insert(order);  // 操作1
    
    try {
        methodB();  // 操作2
    } catch (Exception e) {
        // methodB回滚,但不影响methodA
    }
    
    // 操作1继续,可以提交
}

@Transactional(propagation = Propagation.NESTED)
public void methodB() {
    productMapper.decreaseStock(productId, quantity);
}

// 执行结果:
// methodB使用savepoint(保存点)
// methodB异常 → 回滚到savepoint,methodA可以继续
// methodA异常 → methodB也会回滚

2.4 事务隔离级别

隔离级别脏读不可重复读幻读说明
READ_UNCOMMITTED读到未提交数据
READ_COMMITTEDOracle默认
REPEATABLE_READMySQL默认(InnoDB通过MVCC解决幻读)
SERIALIZABLE串行执行,性能最差

实际项目中的使用:

// 场景1:财务对账(要求数据强一致)
@Transactional(isolation = Isolation.SERIALIZABLE)
public void reconciliation() {
    // 对账逻辑,避免并发问题
}

// 场景2:普通订单查询(用MySQL默认即可)
@Transactional(
    readOnly = true,
    isolation = Isolation.REPEATABLE_READ
)
public Order getOrderById(Long id) {
    return orderMapper.selectById(id);
}

// 场景3:秒杀扣库存(需要避免幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void decreaseStock(Long productId, Integer quantity) {
    // 使用乐观锁或悲观锁
    Product product = productMapper.selectById(productId);
    if (product.getStock() >= quantity) {
        productMapper.decreaseStock(productId, quantity);
    }
}

三、事务失效场景(重点,容易踩坑)

3.1 坑1:类内部方法调用

@Service
public class OrderService {
    
    // ❌ 错误示例
    public void methodA() {
        // 直接调用本类的methodB
        this.methodB();  // 事务不生效!
    }
    
    @Transactional
    public void methodB() {
        orderMapper.insert(order);
    }
}

// 原因:this.methodB()没有通过代理对象调用,直接调用了原始对象
// 解决方案:

解决方案1:注入自己(推荐)

@Service
public class OrderService {
    
    @Autowired
    private OrderService self;  // 注入自己(代理对象)
    
    public void methodA() {
        self.methodB();  // 通过代理对象调用,事务生效
    }
    
    @Transactional
    public void methodB() {
        orderMapper.insert(order);
    }
}

解决方案2:使用AopContext(不推荐,需要开启expose-proxy)

public void methodA() {
    OrderService proxy = (OrderService) AopContext.currentProxy();
    proxy.methodB();
}

解决方案3:拆分到不同类(最佳)

@Service
public class OrderService {
    
    @Autowired
    private OrderTransactionService orderTransactionService;
    
    public void methodA() {
        orderTransactionService.methodB();  // 跨类调用,事务生效
    }
}

@Service
public class OrderTransactionService {
    
    @Transactional
    public void methodB() {
        orderMapper.insert(order);
    }
}

3.2 坑2:方法不是public

// ❌ 错误示例
@Transactional
protected void createOrder() {  // protected、private都不行
    orderMapper.insert(order);
}

// 原因:Spring AOP默认只代理public方法
// 解决方案:改成public

3.3 坑3:异常被捕获

// ❌ 错误示例
@Transactional
public void createOrder() {
    try {
        orderMapper.insert(order);
        productMapper.decreaseStock(productId, quantity);
    } catch (Exception e) {
        log.error("创建订单失败", e);
        // 异常被吃掉,事务不会回滚!
    }
}

// ✅ 正确示例1:重新抛出异常
@Transactional(rollbackFor = Exception.class)
public void createOrder() {
    try {
        orderMapper.insert(order);
        productMapper.decreaseStock(productId, quantity);
    } catch (Exception e) {
        log.error("创建订单失败", e);
        throw e;  // 重新抛出,触发回滚
    }
}

// ✅ 正确示例2:手动回滚
@Transactional(rollbackFor = Exception.class)
public void createOrder() {
    try {
        orderMapper.insert(order);
        productMapper.decreaseStock(productId, quantity);
    } catch (Exception e) {
        log.error("创建订单失败", e);
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        return;  // 手动标记回滚
    }
}

3.4 坑4:抛出的是受检异常

// ❌ 错误示例
@Transactional
public void createOrder() throws Exception {
    orderMapper.insert(order);
    throw new Exception("业务异常");  // 受检异常,不会回滚!
}

// ✅ 正确示例:指定rollbackFor
@Transactional(rollbackFor = Exception.class)
public void createOrder() throws Exception {
    orderMapper.insert(order);
    throw new Exception("业务异常");  // 现在会回滚
}

3.5 坑5:数据库不支持事务

// MySQL的MyISAM引擎不支持事务
// 即使加了@Transactional,也不会生效

// 解决方案:使用InnoDB引擎
CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    ...
) ENGINE=InnoDB;  -- 必须是InnoDB

3.6 坑6:事务传播行为设置错误

// ❌ 错误示例
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void createOrder() {
    orderMapper.insert(order);
    // NOT_SUPPORTED会挂起当前事务,以非事务方式执行
}

// 常见错误:误用REQUIRES_NEW导致数据不一致
@Transactional
public void methodA() {
    orderMapper.insert(order);  // 操作1
    methodB();  // 操作2
    throw new RuntimeException();  // 操作1回滚,但操作2已提交
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
    logMapper.insert(log);
}

3.7 坑7:多线程场景

// ❌ 错误示例
@Transactional
public void createOrder() {
    orderMapper.insert(order);
    
    // 新开线程,事务不会传播到子线程
    new Thread(() -> {
        productMapper.decreaseStock(productId, quantity);
    }).start();
}

// 原因:事务是ThreadLocal的,每个线程独立
// 解决方案:不要在事务方法中开启新线程,或者用消息队列异步处理

四、实战优化经验

4.1 避免大事务

问题:大事务会导致锁持有时间长,影响并发性能

// ❌ 错误示例:大事务
@Transactional
public void processOrder(Long orderId) {
    // 1. 查询订单(可能很慢)
    Order order = orderService.getOrderDetail(orderId);
    
    // 2. 调用外部接口(网络IO,可能超时)
    PayResult result = paymentClient.pay(order);
    
    // 3. 发送短信(外部调用)
    smsService.sendSms(order.getUserPhone(), "订单已支付");
    
    // 4. 更新订单状态
    orderMapper.updateStatus(orderId, OrderStatus.PAID);
}

// ✅ 正确示例:缩小事务范围
public void processOrder(Long orderId) {
    // 1. 查询订单(无需事务)
    Order order = orderService.getOrderDetail(orderId);
    
    // 2. 调用外部接口(无需事务)
    PayResult result = paymentClient.pay(order);
    
    // 3. 只在更新数据库时开启事务
    updateOrderStatus(orderId, OrderStatus.PAID);
    
    // 4. 发送短信(无需事务,异步处理)
    smsService.sendSmsAsync(order.getUserPhone(), "订单已支付");
}

@Transactional(rollbackFor = Exception.class)
public void updateOrderStatus(Long orderId, OrderStatus status) {
    orderMapper.updateStatus(orderId, status);
}

4.2 使用编程式事务(灵活控制)

@Service
public class OrderService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    /**
     * 场景:部分逻辑需要事务,部分不需要
     */
    public void createOrder(Order order) {
        // 1. 参数校验(不需要事务)
        validateOrder(order);
        
        // 2. 调用外部接口(不需要事务)
        PayResult payResult = paymentClient.prePay(order);
        
        // 3. 只在保存数据时使用事务
        Long orderId = transactionTemplate.execute(status -> {
            orderMapper.insert(order);
            orderItemMapper.batchInsert(order.getItems());
            return order.getId();
        });
        
        // 4. 发送通知(不需要事务)
        notificationService.sendOrderNotify(orderId);
    }
    
    /**
     * 场景:需要手动控制回滚
     */
    public void complexBusiness() {
        transactionTemplate.execute(status -> {
            try {
                orderMapper.insert(order);
                productMapper.decreaseStock(productId, quantity);
                return order.getId();
            } catch (StockNotEnoughException e) {
                // 库存不足,手动回滚
                status.setRollbackOnly();
                return null;
            }
        });
    }
}

4.3 只读事务优化

/**
 * 查询场景:使用只读事务
 * 好处:
 * 1. MySQL会优化查询(不加锁)
 * 2. 提示Spring这是只读操作(可以做优化)
 */
@Transactional(readOnly = true)
public List<Order> getOrderList(Long userId) {
    return orderMapper.selectByUserId(userId);
}

/**
 * 复杂查询:关联多张表
 */
@Transactional(
    readOnly = true,
    timeout = 10  // 设置超时,避免慢查询
)
public OrderDetailVO getOrderDetail(Long orderId) {
    Order order = orderMapper.selectById(orderId);
    List<OrderItem> items = orderItemMapper.selectByOrderId(orderId);
    User user = userMapper.selectById(order.getUserId());
    
    return buildOrderDetailVO(order, items, user);
}

4.4 事务超时设置

/**
 * 防止长事务:设置超时时间
 */
@Transactional(
    rollbackFor = Exception.class,
    timeout = 30  // 30秒超时
)
public void importOrders(List<Order> orders) {
    for (Order order : orders) {
        orderMapper.insert(order);
    }
}

/**
 * 批量操作:分批提交
 */
public void batchImport(List<Order> orders) {
    // 分批处理,每批500条
    List<List<Order>> batches = Lists.partition(orders, 500);
    
    for (List<Order> batch : batches) {
        importBatch(batch);  // 每批一个事务
    }
}

@Transactional(rollbackFor = Exception.class, timeout = 10)
private void importBatch(List<Order> batch) {
    orderMapper.batchInsert(batch);
}

4.5 分布式事务方案

场景:跨数据库、跨服务的事务

/**
 * 方案1:本地消息表(最常用)
 */
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
    // 1. 创建订单(本地数据库)
    orderMapper.insert(order);
    
    // 2. 插入消息表(本地数据库,同一个事务)
    LocalMessage message = new LocalMessage();
    message.setTopic("order_created");
    message.setContent(JSON.toJSONString(order));
    message.setStatus(MessageStatus.PENDING);
    localMessageMapper.insert(message);
    
    // 提交事务后,定时任务扫描消息表,发送MQ
}

/**
 * 方案2:Seata分布式事务(复杂场景)
 */
@GlobalTransactional(timeoutMills = 300000)
public void placeOrder(Order order) {
    // 1. 订单服务:创建订单
    orderService.createOrder(order);
    
    // 2. 库存服务:扣减库存(远程调用)
    stockService.decreaseStock(order.getProductId(), order.getQuantity());
    
    // 3. 积分服务:增加积分(远程调用)
    pointService.addPoints(order.getUserId(), order.getAmount());
    
    // 任何一个服务失败,都会全部回滚
}

/**
 * 方案3:最终一致性(推荐)
 */
public void createOrder(Order order) {
    // 1. 创建订单(本地事务)
    Long orderId = createOrderInDb(order);
    
    // 2. 发送MQ消息(异步)
    rocketMQTemplate.asyncSend("order_created_topic", 
        new OrderCreatedEvent(orderId), 
        new SendCallback() {
            @Override
            public void onSuccess(SendResult sendResult) {
                log.info("订单创建事件发送成功");
            }
            
            @Override
            public void onException(Throwable e) {
                log.error("订单创建事件发送失败,需要补偿", e);
                // 记录失败,定时重试
            }
        });
}

@Transactional(rollbackFor = Exception.class)
private Long createOrderInDb(Order order) {
    orderMapper.insert(order);
    return order.getId();
}

五、监控与排查

5.1 事务监控

/**
 * 自定义事务监控切面
 */
@Aspect
@Component
public class TransactionMonitorAspect {
    
    @Around("@annotation(transactional)")
    public Object monitorTransaction(ProceedingJoinPoint pjp, 
                                     Transactional transactional) throws Throwable {
        String methodName = pjp.getSignature().getName();
        long startTime = System.currentTimeMillis();
        
        try {
            Object result = pjp.proceed();
            long duration = System.currentTimeMillis() - startTime;
            
            // 记录事务执行时间
            if (duration > 1000) {
                log.warn("长事务告警: {}耗时{}ms", methodName, duration);
            }
            
            return result;
            
        } catch (Exception e) {
            log.error("事务执行失败: {}", methodName, e);
            throw e;
        }
    }
}

5.2 常见问题排查

-- 1. 查看当前事务
SELECT * FROM information_schema.INNODB_TRX;

-- 2. 查看锁等待
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;

-- 3. 查看长事务
SELECT 
    trx_id,
    trx_state,
    trx_started,
    TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS duration,
    trx_mysql_thread_id,
    trx_query
FROM information_schema.INNODB_TRX
WHERE TIMESTAMPDIFF(SECOND, trx_started, NOW()) > 10
ORDER BY trx_started;

六、面试高频追问

Q1: "@Transactional注解的原理是什么?"

标准答案:

"基于Spring AOP实现。Spring在启动时扫描@Transactional注解,通过JDK动态代理或CGLIB为目标类生成代理对象。代理对象在方法执行前开启事务,执行后提交事务,异常时回滚事务。"

详细解释:

// 1. 启动时,Spring扫描@Transactional注解
// 2. 为带注解的类创建代理(BeanPostProcessor)
// 3. 代理对象拦截方法调用,执行事务逻辑

// 伪代码:
Object proxy = Proxy.newProxyInstance(..., new InvocationHandler() {
    public Object invoke(Object proxy, Method method, Object[] args) {
        // 获取事务属性
        TransactionAttribute txAttr = getTransactionAttribute(method);
        
        // 开启事务
        TransactionInfo txInfo = createTransactionIfNecessary(txAttr);
        
        try {
            // 执行原方法
            Object result = method.invoke(target, args);
            
            // 提交事务
            commitTransactionAfterReturning(txInfo);
            return result;
            
        } catch (Throwable ex) {
            // 回滚事务
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
    }
});

Q2: "事务什么时候会失效?"

标准答案:

"主要有7种情况:1)类内部方法调用;2)方法不是public;3)异常被捕获;4)抛出受检异常但未指定rollbackFor;5)数据库不支持事务;6)传播行为设置错误;7)多线程场景。"

Q3: "REQUIRED和REQUIRES_NEW的区别?"

标准答案:

"REQUIRED:如果当前有事务就加入,没有就新建。REQUIRES_NEW:无论当前有没有事务,都会新建一个事务,并挂起当前事务。主要区别是REQUIRES_NEW会创建独立的事务,不受外部事务影响。"

实战场景:

// 记录操作日志:无论业务成功失败,日志都要保存
// 所以日志使用REQUIRES_NEW

@Transactional
public void createOrder() {
    orderMapper.insert(order);
    
    // 日志独立事务,无论订单创建成功还是失败,日志都会保存
    logService.saveLog("创建订单");
    
    // 如果这里抛异常,订单回滚,但日志已保存
    productMapper.decreaseStock(productId, quantity);
}

Q4: "如何避免大事务?"

标准答案:

"1) 缩小事务范围,只在必要时开启事务;2) 使用编程式事务,精确控制事务边界;3) 异步处理非核心逻辑;4) 批量操作分批提交;5) 避免在事务中调用RPC、HTTP等外部接口。"

Q5: "分布式事务怎么处理?"

标准答案:

"实际项目中,我们优先选择最终一致性方案,通过MQ保证数据最终一致。具体有:1)本地消息表;2)事务消息(RocketMQ);3)Saga模式。强一致性场景才考虑Seata等分布式事务框架,但要评估性能影响。"


七、实战案例(真实经历)

案例1:订单创建事务回滚,但积分已扣除

问题背景: 用户下单时,订单创建失败,但积分已经被扣除了。

问题代码:

@Service
public class OrderService {
    
    @Autowired
    private PointService pointService;
    
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Order order) {
        // 1. 扣积分(调用另一个服务)
        pointService.deductPoints(order.getUserId(), order.getPoints());
        
        // 2. 创建订单
        orderMapper.insert(order);
        
        // 3. 扣库存
        int rows = productMapper.decreaseStock(
            order.getProductId(), 
            order.getQuantity()
        );
        
        if (rows == 0) {
            throw new BusinessException("库存不足");  // 抛异常回滚
        }
    }
}

@Service
public class PointService {
    
    // ❌ 问题:使用了REQUIRES_NEW
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void deductPoints(Long userId, Integer points) {
        pointMapper.deduct(userId, points);
    }
}

问题原因:

  • PointService.deductPoints 使用了 REQUIRES_NEW
  • 它创建了独立的事务,立即提交
  • 当订单创建失败回滚时,积分扣除不会回滚

解决方案:

// 方案1:改用REQUIRED(推荐)
@Transactional(propagation = Propagation.REQUIRED)
public void deductPoints(Long userId, Integer points) {
    pointMapper.deduct(userId, points);
}

// 方案2:调整顺序,先创建订单,成功后再扣积分
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
    // 1. 先创建订单
    orderMapper.insert(order);
    
    // 2. 扣库存
    productMapper.decreaseStock(order.getProductId(), order.getQuantity());
    
    // 3. 最后扣积分(成功才扣)
    pointService.deductPoints(order.getUserId(), order.getPoints());
}

// 方案3:使用消息队列(最佳)
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) {
    // 1. 创建订单
    orderMapper.insert(order);
    
    // 2. 扣库存
    productMapper.decreaseStock(order.getProductId(), order.getQuantity());
    
    // 3. 发送MQ消息,异步扣积分
    rocketMQTemplate.sendMessageInTransaction(
        "point_deduct_topic",
        new PointDeductEvent(order.getUserId(), order.getPoints()),
        order
    );
}

效果:

  • 方案1:订单和积分在同一个事务,要么都成功,要么都失败
  • 方案3:订单创建成功后,通过MQ异步扣积分,支持重试

案例2:类内部方法调用导致事务失效

问题背景: 用户注册时,明明加了@Transactional,但数据还是插入了一半。

问题代码:

@Service
public class UserService {
    
    public void register(UserRegisterDTO dto) {
        // 调用本类的事务方法
        this.saveUser(dto);  // ❌ 事务失效
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void saveUser(UserRegisterDTO dto) {
        // 1. 创建用户
        userMapper.insert(new User(dto));
        
        // 2. 创建用户详情
        userDetailMapper.insert(new UserDetail(dto));
        
        // 3. 初始化账户
        accountMapper.insert(new Account(dto));
        
        // 如果这里抛异常,前面的数据不会回滚!
        throw new RuntimeException("test");
    }
}

问题原因:

  • this.saveUser() 是直接调用,没有通过代理对象
  • 事务切面无法拦截,事务不生效

解决方案:

// 方案1:拆分到不同类(推荐)
@Service
public class UserService {
    
    @Autowired
    private UserTransactionService userTransactionService;
    
    public void register(UserRegisterDTO dto) {
        // 跨类调用,事务生效
        userTransactionService.saveUser(dto);
    }
}

@Service
public class UserTransactionService {
    
    @Transactional(rollbackFor = Exception.class)
    public void saveUser(UserRegisterDTO dto) {
        userMapper.insert(new User(dto));
        userDetailMapper.insert(new UserDetail(dto));
        accountMapper.insert(new Account(dto));
    }
}

// 方案2:注入自己
@Service
public class UserService {
    
    @Autowired
    private UserService self;  // 注入代理对象
    
    public void register(UserRegisterDTO dto) {
        self.saveUser(dto);  // 通过代理调用,事务生效
    }
    
    @Transactional(rollbackFor = Exception.class)
    public void saveUser(UserRegisterDTO dto) {
        userMapper.insert(new User(dto));
        userDetailMapper.insert(new UserDetail(dto));
        accountMapper.insert(new Account(dto));
    }
}

效果:

  • 事务生效,要么全部成功,要么全部回滚

八、最佳实践总结

✅ DO(应该做的)

  1. 明确指定rollbackFor

    @Transactional(rollbackFor = Exception.class)
    
  2. 缩小事务范围

    // 只在必要时开启事务
    public void process() {
        // 查询、校验(无需事务)
        validate();
        
        // 只在更新数据时用事务
        updateData();
        
        // 发送通知(无需事务)
        notify();
    }
    
  3. 设置超时时间

    @Transactional(timeout = 30)
    
  4. 只读事务优化

    @Transactional(readOnly = true)
    public List<Order> query() { ... }
    
  5. 监控长事务

    // 通过日志、监控工具发现长事务
    

❌ DON'T(不应该做的)

  1. 不要在事务中调用RPC、HTTP

    // ❌ 错误
    @Transactional
    public void process() {
        orderMapper.insert(order);
        paymentService.pay();  // 外部调用,可能很慢
    }
    
  2. 不要在事务中执行大量循环

    // ❌ 错误
    @Transactional
    public void batchProcess(List<Order> orders) {
        for (Order order : orders) {  // 可能几万条
            orderMapper.insert(order);
        }
    }
    
    // ✅ 正确:分批
    
  3. 不要在事务方法中开启新线程

    // ❌ 错误
    @Transactional
    public void process() {
        new Thread(() -> {
            orderMapper.insert(order);  // 事务不会传播到子线程
        }).start();
    }
    
  4. 不要滥用事务

    // ❌ 错误:只读方法不需要写事务
    @Transactional
    public Order getById(Long id) {
        return orderMapper.selectById(id);
    }
    
    // ✅ 正确
    @Transactional(readOnly = true)
    public Order getById(Long id) {
        return orderMapper.selectById(id);
    }
    

九、面试回答模板

第一步:表明态度(10秒)

"我在项目中用得比较多,主要使用@Transactional声明式事务。"

第二步:基本使用(30秒)

"最常用的是在Service方法上加@Transactional注解,保证多表操作的原子性。比如创建订单时,需要插入订单表、扣减库存、更新账户,这些操作要么全成功,要么全失败。"

第三步:关键配置(30秒)

"我会特别注意几个属性:rollbackFor设置为Exception.class(因为默认只回滚RuntimeException),timeout设置超时时间避免长事务,readOnly用于查询方法优化性能。"

第四步:实战经验(1-2分钟)

"我踩过几个坑:1)类内部方法调用事务失效,后来拆分到不同类解决;2)异常被捕获导致不回滚,改成重新抛出异常;3)大事务影响性能,优化成只在必要时开启事务。"

第五步:加分项(可选)

"分布式场景下,我们用RocketMQ的事务消息保证最终一致性,比如订单创建成功后,通过MQ异步扣积分、扣库存。"


十、参考资料


祝你面试顺利!记住:结合实际项目经验,比单纯背理论更有说服力。