@Transactional做不到的5件事,我用这6种方法解决了

@Transactional做不到的5件事,我用这6种方法解决了

看Mall项目订单代码时发现:一个方法操作6张表,14步业务逻辑,全在一个事务里,居然没炸。

研究了两天,发现了6种比@Transactional更灵活的玩法。写了个demo项目验证了一遍。

我们要解决的痛点

日常开发中,@Transactional解决不了的几个问题:

  1. 库存不足时:想保留订单记录标记"待补货",但不知道怎么不回滚
  2. 发MQ消息:在事务里发了消息,结果事务回滚了,消息却发出去了
  3. 批量操作:100个订单发货,1个失败就全部回滚,但其实想让成功的继续
  4. 记录日志:业务失败了也想记录日志,但事务回滚了日志也没了
  5. 隔离级别/超时 :不知道@Transactional那些参数怎么用

这篇文章会用实际代码演示6种解决方案。

目录

关于demo项目

本文代码:gitee.com/sh_wangwanb...

特点:集成测试框架,通过反射自动构建参数,启动即测试,自动生成markdown报告。不用手动准备数据,不用一个个跑测试用例。

导入数据库脚本(doc/simple-transactional-init.sql),改下配置,启动项目就能看到完整测试结果。


编程式事务:区分业务失败和系统异常

这是我在Mall里发现的一个场景:订单创建后要调用风控服务检查。

  • 风控不通过(业务规则):订单要保留,标记"待审核",人工复核
  • 风控服务挂了(系统故障):订单要回滚,不能留脏数据

@Transactional做不到。因为它只能靠抛异常触发回滚,无法区分这两种情况。

TransactionTemplate可以动态控制

java 复制代码
public OrderResult createOrder(OrderParam param) {
    
    return transactionTemplate.execute(status -> {
        try {
            // 1. 创建订单
            Order order = buildOrder(param);
            orderMapper.insert(order);
            
            // 2. 创建订单商品
            List<OrderItem> items = buildOrderItems(order);
            orderItemMapper.batchInsert(items);
            
            // 3. 锁定库存
            lockStock(param.getItems());
            
            // 4. 调用风控服务检查
            RiskCheckResult riskResult = riskService.check(order);
            
            if (!riskResult.isPass()) {
                // 风控不通过 - 业务失败,但不回滚
                order.setStatus(OrderStatus.WAIT_AUDIT);  // 待审核
                order.setNote("风控检查未通过:" + riskResult.getReason());
                orderMapper.updateById(order);
                
                // 关键:不调用 status.setRollbackOnly()
                // 订单和商品明细都会保留
                return OrderResult.fail("订单需人工审核");
            }
            
            // 风控通过,订单正常
            return OrderResult.success(order.getId());
            
        } catch (RiskServiceException e) {
            // 风控服务异常 - 系统故障,必须回滚
            log.error("风控服务异常", e);
            status.setRollbackOnly();
            return OrderResult.error("系统异常,请稍后重试");
            
        } catch (Exception e) {
            // 其他异常也回滚
            status.setRollbackOnly();
            return OrderResult.error(e.getMessage());
        }
    });
}

画个图就明白了

graph TD A[开始事务] --> B[创建订单] B --> C[创建订单商品] C --> D[锁定库存] D --> E[调用风控服务] E --> F{风控结果?} F -->|不通过-业务失败| G[更新订单状态=待审核] G --> H[提交事务] H --> I[订单保留,状态=待审核] F -->|通过| J[提交事务] J --> K[订单正常创建] F -->|服务异常-系统故障| L[setRollbackOnly] L --> M[回滚事务] M --> N[订单被删除]

这才是编程式事务的价值

场景 @Transactional TransactionTemplate
风控不通过 抛异常→全回滚 不回滚,保留订单
风控服务挂了 抛异常→全回滚 回滚,不留脏数据
库存不足 抛异常→全回滚 保留订单,标记"待补货"

核心区别:能区分"业务失败"和"系统异常",动态决定要不要回滚。

我测试了一下:

bash 复制代码
# 测试风控不通过(高金额订单)
POST /programmatic/risk-check

# 结果
订单ID:8
订单状态:待审核
订单备注:风控检查未通过:金额过高
数据库:订单和商品明细都保留了

这玩意儿我之前真不知道能这么用。

@Transactional的参数,我被坑过

Mall的商品创建方法是这么写的:

java 复制代码
@Transactional(
    isolation = Isolation.REPEATABLE_READ,    
    propagation = Propagation.REQUIRED,      
    timeout = 30,                            
    rollbackFor = Exception.class            
)
public int createProduct(ProductParam param) {
    // 插入8张表...
}

我之前都是直接@Transactional,从来不加参数。后来踩了几次坑才知道这些参数的用处。

isolation这个参数要注意

有次数据库从MySQL换成PostgreSQL,突然出现了幻读问题。

原因是:

  • MySQL默认 REPEATABLE_READ(可重复读)
  • PostgreSQL默认 READ_COMMITTED(读已提交)

如果代码里没显式指定隔离级别,换数据库就可能出问题。

所以建议:

java 复制代码
// 明确指定隔离级别,不依赖数据库默认值
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void someMethod() {
    // 避免环境切换导致行为变化
}

timeout和rollbackFor简单说两句

timeout:防止长事务锁表

java 复制代码
@Transactional(timeout = 30)  // 30秒超时
public void complexTask() {
    // ...
}

rollbackFor:Spring默认只有RuntimeException才回滚,Checked Exception不回滚

java 复制代码
@Transactional(rollbackFor = Exception.class)  // 明确指定
public void createOrder() throws Exception {
    // ...
}

这两个参数记得加上,能避免很多坑。

事务提交后发MQ,我之前都做错了

订单创建成功后,要发个MQ消息(30分钟后自动取消未支付订单)。

我之前是这么写的:

java 复制代码
@Transactional
public void createOrder() {
    orderMapper.insert(order);
    
    // 直接发MQ
    mqSender.send("order.cancel.delay", order.getId());
}

看起来没问题吧?实际上有个致命问题。

问题出在时机上

画个图就明白了:

sequenceDiagram participant S as Service participant DB as Database participant MQ as RabbitMQ rect rgb(255, 240, 240) Note over S,MQ: 错误的做法 S->>DB: 1. INSERT订单 Note over DB: 数据在事务内
还没提交 S->>MQ: 2. 发送MQ消息 Note over MQ: 消息已发出 S->>DB: 3. 后面某步失败 DB-->>S: 4. 事务回滚 Note over DB: 订单被删除 Note over MQ,DB: 问题:消息发了
但数据没了 end

问题本质:MQ消息发出去了,但事务回滚了,订单根本不存在。30分钟后消费者去取消订单,发现订单不存在。

这就是副作用的时机与事务一致性问题:

  • 订单插入、库存扣减 → 在同一个事务里,要么全成功,要么全回滚
  • MQ消息 → 不在这个事务里,发出去就收不回来了

事务同步器解决这个问题

Spring提供了事务生命周期的钩子,让你在特定阶段执行回调:

java 复制代码
@Transactional
public void createOrder() {
    orderMapper.insert(order);
    
    // 注册事务同步器
    TransactionSynchronizationManager.registerSynchronization(
        new TransactionSynchronization() {
            
            @Override
            public void afterCommit() {
                // 只有事务提交成功,这里才会执行
                mqSender.send("order.cancel.delay", order.getId());
                log.info("MQ消息已发送");
            }
            
            @Override
            public void afterCompletion(int status) {
                if (status == STATUS_ROLLED_BACK) {
                    log.info("事务回滚,MQ消息不会发送");
                }
            }
        }
    );
}

现在的时序是这样:

sequenceDiagram participant S as Service participant DB as Database participant MQ as RabbitMQ rect rgb(240, 255, 240) Note over S,MQ: 正确的做法 S->>DB: 1. INSERT订单 S->>S: 2. 注册afterCommit回调 alt 事务成功 S->>DB: 3. COMMIT Note over DB: 数据已持久化 S->>MQ: 4. 触发afterCommit
发送MQ消息 Note over MQ: 数据和消息一致 else 事务失败 S->>DB: 3. ROLLBACK Note over DB: 数据被删除 Note over S: afterCommit不执行 Note over MQ: 消息不会发送 end end

核心区别:只有订单真正提交到数据库后,才发MQ消息。事务回滚了,消息就不发。

4个生命周期钩子

事务同步器提供了4个回调点:

java 复制代码
TransactionSynchronizationManager.registerSynchronization(
    new TransactionSynchronization() {
        
        @Override
        public void beforeCommit(boolean readOnly) {
            log.info("【阶段1-beforeCommit】事务即将提交");
            // 最后的数据校验
        }
        
        @Override
        public void beforeCompletion() {
            log.info("【阶段2-beforeCompletion】事务即将完成");
            // 清理临时资源
        }
        
        @Override
        public void afterCommit() {
            log.info("【阶段3-afterCommit】事务已提交");
            // 发MQ、清缓存(数据已持久化)
        }
        
        @Override
        public void afterCompletion(int status) {
            String statusStr = (status == STATUS_COMMITTED) ? "提交" : "回滚";
            log.info("【阶段4-afterCompletion】事务已完成,状态:{}", statusStr);
        }
    }
);

执行顺序是固定的:

graph TD A[开始事务] --> B[业务逻辑执行] B --> C{要提交?} C -->|是| D[beforeCommit] D --> E[beforeCompletion] E --> F[COMMIT] F --> G[afterCommit] G --> H[afterCompletion状态COMMITTED] C -->|否| I[beforeCompletion] I --> J[ROLLBACK] J --> K[afterCompletion状态ROLLED_BACK]

哪些场景必须用afterCommit

所有"对外的副作用"都应该放在afterCommit里:

场景1:发MQ消息

java 复制代码
@Override
public void afterCommit() {
    // 延迟取消订单
    mqSender.send("order.cancel.delay", orderId);
}

场景2:清理缓存

java 复制代码
@Override
public void afterCommit() {
    // 清理商品缓存
    redisTemplate.delete("product:" + productId);
}

场景3:记录日志到另一个库

java 复制代码
@Override
public void afterCommit() {
    // 写到日志库(不在当前事务)
    logMapper.insert(businessLog);
}

场景4:调用外部服务

java 复制代码
@Override
public void afterCommit() {
    // 通知第三方
    thirdPartyService.notify(order);
}

核心原则:只有订单数据真正持久化了,外部世界才能知道。

同库的日志也要用afterCommit吗?

理论上,如果日志表和订单表在同一个数据库、同一个事务里,写早了会一起回滚,不会有问题。

但实际业务中,我们希望:

  1. 解耦:订单业务和日志记录分离
  2. 性能:日志操作不影响主事务耗时
  3. 重试:日志失败可以独立重试,不影响订单

所以建议还是放在afterCommit里。

我测试了一下

bash 复制代码
# 运行测试
POST /synchronization/phases

# 控制台输出
【阶段1-beforeCommit】事务即将提交
【阶段2-beforeCompletion】事务即将完成
【阶段3-afterCommit】事务已提交
【阶段4-afterCompletion】事务已完成,状态:提交
MQ消息已发送

顺序是固定的,非常可靠。

事务事件监听:解耦副作用操作

订单创建成功后,要做3件事:发MQ、记录日志、发通知。

如果都写在一个方法里,代码会很臃肿:

java 复制代码
@Transactional
public void createOrder() {
    orderMapper.insert(order);
    
    // 业务逻辑越来越多
    mqSender.send(...);
    logService.save(...);
    notifyService.send(...);
}

而且事务范围太大了,发短信也在事务里?

事务事件监听可以解耦

第1步:定义事件

java 复制代码
@Getter
@AllArgsConstructor
public class OrderCreatedEvent {
    private String orderSn;
    private Long memberId;
    private BigDecimal amount;
}

第2步:发布事件

java 复制代码
@Service
public class OrderService {
    
    @Autowired
    private ApplicationEventPublisher eventPublisher;
    
    @Transactional
    public void createOrder(OrderParam param) {
        // 创建订单
        orderMapper.insert(order);
        
        // 发布事件(立即发布,但监听器何时处理取决于监听方式)
        OrderCreatedEvent event = new OrderCreatedEvent(
            order.getOrderSn(),
            order.getMemberId(),
            order.getTotalAmount()
        );
        eventPublisher.publishEvent(event);
        
        log.info("事件已发布");
    }
}

第3步:监听事件

java 复制代码
@Component
public class OrderEventListener {
    
    // 关键:@TransactionalEventListener + AFTER_COMMIT
    // 事件会被"挂起",等事务提交成功后才处理
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderCreated(OrderCreatedEvent event) {
        log.info("监听到订单创建:{}", event.getOrderSn());
        
        // 这些操作在事务提交后才执行
        mqSender.send("order.cancel", event.getOrderSn());
        logMapper.insert(log);
        notifyService.send(event.getMemberId());
    }
    
    // 事务回滚后执行
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleOrderFailed(OrderCreatedEvent event) {
        log.info("订单创建失败:{}", event.getOrderSn());
    }
}

事件发布与事务的关系

这里容易混淆的点:publishEvent本身与事务无关,但监听器的执行时机取决于监听方式。

画个图说明:

sequenceDiagram participant S as OrderService participant E as EventPublisher participant L1 as @EventListener
普通监听器 participant L2 as @TransactionalEventListener
AFTER_COMMIT participant DB as Database rect rgb(255, 245, 240) Note over S,DB: 方法有 @Transactional S->>DB: 1. INSERT订单 S->>E: 2. publishEvent(event) E->>L1: 3. 立即同步调用 Note over L1: 普通监听器立即执行
此时事务还没提交 E->>L2: 4. 事件挂起 Note over L2: AFTER_COMMIT监听器不执行
等待事务提交 alt 事务提交成功 S->>DB: 5. COMMIT DB->>L2: 6. 触发AFTER_COMMIT Note over L2: 监听器执行
数据已持久化 else 事务回滚 S->>DB: 5. ROLLBACK Note over L2: AFTER_COMMIT不执行 end end

关键区别

监听方式 执行时机 事务回滚影响
@EventListener 立即执行 已执行的副作用无法撤销
@TransactionalEventListener(AFTER_COMMIT) 事务提交后 事务回滚则不执行
@TransactionalEventListener(AFTER_ROLLBACK) 事务回滚后 只有回滚才执行

4个事务阶段

java 复制代码
// 提交前(做最后校验)
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
public void beforeCommit(OrderCreatedEvent event) {
    // 事务即将提交,可以做最后校验
}

// 提交后(发副作用)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void afterCommit(OrderCreatedEvent event) {
    // 数据已持久化,可以安全地发MQ、清缓存
}

// 回滚后(记录失败)
@TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
public void afterRollback(OrderCreatedEvent event) {
    // 事务失败了,记录失败日志
}

// 完成后(清理资源)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
public void afterCompletion(OrderCreatedEvent event) {
    // 无论成功失败都会执行
}

几个要注意的地方

1. 必须在事务方法里发布

java 复制代码
// 错误:方法没有 @Transactional
public void createOrder() {
    orderMapper.insert(order);
    eventPublisher.publishEvent(event);  // AFTER_COMMIT监听器不会触发!
}

// 正确:方法有 @Transactional
@Transactional
public void createOrder() {
    orderMapper.insert(order);
    eventPublisher.publishEvent(event);  // 监听器会在提交后触发
}

2. 子事务的事件跟随子事务

java 复制代码
@Transactional
public void parentMethod() {
    // 父事务
    
    childMethod();  // 子事务(REQUIRES_NEW)
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void childMethod() {
    orderMapper.insert(order);
    eventPublisher.publishEvent(event);  // 监听器跟随子事务的提交
}

3. 如果没有事务怎么办

java 复制代码
// 监听器默认不执行,除非加 fallbackExecution=true
@TransactionalEventListener(
    phase = TransactionPhase.AFTER_COMMIT,
    fallbackExecution = true  // 没有事务也会执行
)
public void handleEvent(OrderCreatedEvent event) {
    // ...
}

对比事务同步器

方式 代码耦合度 扩展性 适用场景
TransactionSynchronization 高(在方法里注册) 简单场景,1-2个操作
@TransactionalEventListener 低(发布订阅) 复杂场景,多个操作

我的建议

  • 只有1-2个操作,用TransactionSynchronization
  • 有多个操作,或者可能扩展,用@TransactionalEventListener

好处是代码解耦了,要加新功能,写个监听器就行,不用改原方法。

批量操作必须用手动事务

批量发货100个订单,其中1个失败了咋办?

如果用@Transactional

java 复制代码
@Transactional
public void batchDelivery(List<Long> orderIds) {
    for (Long orderId : orderIds) {
        // 发货逻辑
    }
}

问题:100个订单在一个事务里,1个失败全部回滚。

但实际需求是:成功的正常发货,失败的记录下来。

用PlatformTransactionManager手动控制

java 复制代码
@Service
public class OrderBatchService {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    public BatchResult batchDelivery(List<Long> orderIds) {
        
        List<Long> success = new ArrayList<>();
        List<String> failed = new ArrayList<>();
        
        for (Long orderId : orderIds) {
            // 每个订单一个独立事务
            DefaultTransactionDefinition def = new DefaultTransactionDefinition();
            TransactionStatus status = transactionManager.getTransaction(def);
            
            try {
                // 发货逻辑
                Order order = orderMapper.selectById(orderId);
                order.setStatus(2);  // 已发货
                orderMapper.updateById(order);
                reduceStock(order);
                
                // 手动提交
                transactionManager.commit(status);
                success.add(orderId);
                
            } catch (Exception e) {
                // 手动回滚
                transactionManager.rollback(status);
                failed.add("订单" + orderId + ":" + e.getMessage());
            }
        }
        
        return new BatchResult(success, failed);
    }
}

高级用法:设置事务属性

对于定时任务、后台批处理这种场景,可以显式控制事务属性:

java 复制代码
public BatchResult batchCloseOrder(List<Long> orderIds) {
    
    for (Long orderId : orderIds) {
        DefaultTransactionDefinition def = new DefaultTransactionDefinition();
        
        // 强制新事务(无论外层是否有事务)
        def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        
        // 降低隔离级别,减少锁争用
        def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        
        // 设置超时,避免长事务阻塞
        def.setTimeout(10);
        
        TransactionStatus status = transactionManager.getTransaction(def);
        
        try {
            Order order = orderMapper.selectById(orderId);
            order.setStatus(4);  // 已关闭
            orderMapper.updateById(order);
            
            transactionManager.commit(status);
            
        } catch (Exception e) {
            transactionManager.rollback(status);
        }
    }
}

三种方式对比

方式 事务范围 一条失败影响 适用场景
@Transactional 整个批次 全部回滚 不适合批量
手动事务(默认属性) 每条独立 只回滚这条 普通批处理
手动事务(定制属性) 每条独立 只回滚这条 高并发批处理

我测试了100个订单,97个成功,3个失败。成功的都发货了,失败的记录下来了。

核心价值:每条数据独立事务,部分失败不影响其他。

事务传播机制:3种常用场景

创建订单时,要调另一个方法插入订单商品。两个方法都有@Transactional,会咋样?

7种传播机制,常用的是3种:REQUIRED、REQUIRES_NEW、NESTED。

REQUIRED(默认):同成同败

行为:有事务就加入,没有就新建。父子方法共享同一个事务。

java 复制代码
// 父方法
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
    orderMapper.insert(order);
    
    createOrderItems(order.getId());  // 加入当前事务
}

// 子方法
@Transactional(propagation = Propagation.REQUIRED)
public void createOrderItems(Long orderId) {
    itemMapper.batchInsert(items);
}

关键点

  • 父子方法在同一个事务里
  • 子方法抛异常 → 整个事务回滚(父也一起回滚)
  • 订单和订单商品"同成同败"
graph LR A[createOrder开启事务] --> B[insert order] B --> C[createOrderItems加入事务] C --> D[insert items] D --> E{子方法异常?} E -->|是| F[整个事务回滚] E -->|否| G[事务提交]

适用场景:一个业务流程内的多步骤需要"同成同败"。80%的场景都用这个。

REQUIRES_NEW:独立事务

行为:挂起当前事务,开启一个全新的事务,独立提交/回滚。

java 复制代码
// 父方法
@Transactional
public void createOrder() {
    orderMapper.insert(order);
    
    logService.saveLog(log);  // 新事务,独立提交
    
    // 后面的代码可能失败
}

// 子方法
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveLog(Log log) {
    logMapper.insert(log);
}

关键点

  • 子方法失败只影响子事务,父事务不受影响
  • 父事务后续回滚,子事务已提交的结果也保留
  • 日志一定会保存,即使订单创建失败
graph TD A[createOrder事务1] --> B[insert order] B --> C[挂起事务1] C --> D[saveLog开启事务2] D --> E[insert log] E --> F[事务2提交-日志已保存] F --> G[恢复事务1] G --> H{事务1继续执行} H -->|成功| I[事务1提交] H -->|失败| J[事务1回滚-但日志保留]

适用场景:必须独立持久化的动作,如:

  • 记录审计日志
  • 写消息表
  • 发送通知记录

即使主流程失败也不能丢。

NESTED:局部回滚

行为:在同一物理事务内使用"保存点"(Savepoint),子方法相当于子事务。

java 复制代码
// 父方法
@Transactional
public void createOrder() {
    orderMapper.insert(order);
    
    try {
        createGift(order.getId());  // 嵌套事务
    } catch (Exception e) {
        // 赠品创建失败,但订单继续
        log.warn("赠品创建失败,继续处理订单");
    }
    
    // 订单正常提交
}

// 子方法
@Transactional(propagation = Propagation.NESTED)
public void createGift(Long orderId) {
    giftMapper.insert(gift);
}

关键点

  • 子方法回滚只回滚到保存点,不影响父方法已做的操作
  • 父方法回滚会连同子方法一起回滚
  • 需要数据库支持保存点(InnoDB支持)
graph TD A[createOrder事务开启] --> B[insert order] B --> C[创建保存点] C --> D[createGift嵌套事务] D --> E{赠品创建} E -->|失败| F[回滚到保存点] F --> G[订单保留] G --> H[事务提交] E -->|成功| I[继续执行] I --> H

适用场景:主流程可继续,但某个子步骤允许"局部失败回滚"。

  • 批处理中某条失败不影响前面已写入的步骤
  • 赠品、优惠券等可选功能

注意

  • 需要使用DataSourceTransactionManager(JPA的不支持)
  • 数据库必须支持保存点(InnoDB支持,MyISAM不支持)

三种传播行为对比

传播行为 事务关系 子方法失败影响 父方法失败影响 典型场景
REQUIRED 共享事务 整个事务回滚 整个事务回滚 订单+订单商品
REQUIRES_NEW 独立事务 只回滚子事务 子事务已提交 审计日志
NESTED 保存点 回滚到保存点 整个事务回滚 赠品、优惠券

选型建议

  • 默认用REQUIRED:80%的场景都是"同成同败"
  • 需要独立落盘的用REQUIRES_NEW:审计日志、消息表
  • 需要局部回滚的用NESTED:可选功能、批处理

我测试了一下,这3种传播行为都符合预期。

几个要注意的地方

事务范围要小

java 复制代码
// 不好的写法
@Transactional
public void process() {
    List<Data> data = queryBigData();   // 慢查询,不需要事务
    Data result = calculate(data);      // 计算,不需要事务
    mapper.save(result);                // 真正需要事务
}

// 改成这样
public void process() {
    List<Data> data = queryBigData();
    Data result = calculate(data);
    saveInTransaction(result);
}

@Transactional
private void saveInTransaction(Data data) {
    mapper.save(data);
}

只把写操作放事务里。

批量插入要用batchInsert

java 复制代码
// 慢
@Transactional
public void save(List<Item> items) {
    for (Item item : items) {
        mapper.insert(item);  // N次数据库访问
    }
}

// 快
@Transactional
public void save(List<Item> items) {
    mapper.batchInsert(items);  // 1次数据库访问
}

我之前不知道这个,踩过坑。1000条数据,循环插入要10秒,批量插入只要0.5秒。

长事务要设置超时

java 复制代码
@Transactional(timeout = 30)
public void longTask() {
    // 防止锁表
}

生产环境一定要加这个。

总结一下

这6种玩法,每个都能解决实际问题:

  1. 编程式事务 → 库存不足保留订单
  2. @Transactional参数 → 隔离级别、超时、回滚规则
  3. 事务同步器 → 事务提交后发MQ
  4. 事务事件监听 → 解耦业务逻辑
  5. 手动控制事务 → 批量操作
  6. 事务传播机制 → 日志记录、赠品创建

80%的场景,@Transactional就够了。遇到特殊情况,再用对应的高级用法。

别过度设计,够用就行。

代码在这里

所有代码都是可以跑的,有完整测试用例。

数据库脚本在 doc/simple-transactional-init.sql,导入就能用。


你们平时用Spring事务都遇到过什么坑?

或者有什么好的实践经验?

欢迎在评论区聊聊,我也想学习学习。

特别是事务传播机制那块,我自己还没完全搞透。如果有大佬愿意指点一下,那就太好了。


如果这篇文章对你有帮助,麻烦点个赞👍,让更多人看到。

这篇文章从研究Mall源码到写demo,再到写文章、画图、测试,前后花了两天时间。

希望能帮你解决实际问题。

相关推荐
清风徐来QCQ1 小时前
Spring Boot 静态资源路径映射
java·spring boot·后端
程序定小飞1 小时前
基于springboot的体育馆使用预约平台的设计与实现
java·开发语言·spring boot·后端·spring
计算机毕业设计小途2 小时前
计算机毕业设计推荐:基于SpringBoot的水产养殖管理系统【Java+spring boot+MySQL、Java项目、Java毕设、Java项目定制定做】
java·spring boot·mysql
s***4532 小时前
解决Spring Boot中Druid连接池“discard long time none received connection“警告
spring boot·后端·oracle
Dreamboat-L2 小时前
IDEA中在springboot项目中整合Mybatis时@Autowired时,提示Could not autowire解决方案
spring boot·intellij-idea·mybatis
IT_陈寒2 小时前
Python性能提升50%:这5个隐藏技巧让你的代码快如闪电⚡
前端·人工智能·后端
yoke菜籽2 小时前
面试150——区间
面试·职场和发展
自由生长20243 小时前
Protocol Buffers 技术解析:为什么叫「协议缓冲区」
后端