找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

465

积分

0

好友

57

主题
发表于 8 小时前 | 查看: 1| 回复: 0

在研究Mall项目的订单代码时,我发现一个有趣的现象:一个方法内操作了6张表,包含14步业务逻辑,这些操作全部放在一个事务里,居然没有出现问题。

经过深入研究,我发现了6种比单纯使用 @Transactional 注解更灵活的事务控制方法,并创建了一个demo项目进行验证。

我们要解决的痛点

在日常开发中,@Transactional 注解无法妥善处理以下几个典型场景:

  1. 库存不足时:希望保留订单记录并标记为“待补货”,但不知道如何让事务不回滚。
  2. 发MQ消息:在事务内发送消息,事务回滚后消息却已经发出,导致数据不一致。
  3. 批量操作:处理100个订单发货,其中一个失败就导致全部回滚,但我们希望成功的订单能继续处理。
  4. 记录日志:即使业务失败,也希望记录下操作日志,但事务回滚会导致日志一同被清除。
  5. 隔离级别与超时:不清楚 @Transactional 的各类参数应如何配置与使用。

本文将用实际代码演示这6种高级事务控制方案的实现。

目录

  • 编程式事务:精确区分业务失败与系统异常
  • @Transactional参数详解:隔离级别与超时控制
  • 事务同步器:确保事务提交后才发送MQ
  • 事务事件监听:解耦业务主逻辑与副作用操作
  • 手动控制事务:实现灵活的批量操作
  • 事务传播机制:3种最常用场景解析

关于demo项目
本文所有示例代码均来自项目:https://gitee.com/sh_wangwanbao/simple-transactional
项目特点:集成了测试框架,可通过反射自动构建测试参数,启动即可运行测试并自动生成Markdown报告。无需手动准备测试数据,也无需逐个执行测试用例。
使用方法:导入数据库脚本(doc/simple-transactional-init.sql),修改配置文件后启动项目,即可查看完整的测试结果。

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

这是我在Mall项目中遇到的一个实际场景:订单创建后需要调用风控服务进行检查。

  • 风控不通过(业务规则):订单需要保留,标记为“待审核”,等待人工复核。
  • 风控服务挂掉(系统故障):订单必须回滚,不能留下脏数据。

仅使用 @Transactional 注解无法做到这一点。因为它只能通过抛出异常来触发回滚,无法区分这两种截然不同的情况。

TransactionTemplate 实现动态控制

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());
        }
    });
}

这才是编程式事务的价值

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

核心区别:能够精确区分“业务规则失败”和“系统异常故障”,并动态决定是否回滚。

我进行的测试结果如下:

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

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

编程式事务的这种精细控制能力,在传统的声明式事务中是无法实现的。

@Transactional的参数,我被坑过

在Mall项目中,商品创建方法是这么写的:

@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(读已提交)。

如果代码中没有显式指定隔离级别,更换数据库环境就可能导致程序行为出现差异,引发难以排查的问题。

因此建议:

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

timeout 和 rollbackFor 参数简述

timeout:防止长事务锁表,影响系统性能。

@Transactional(timeout = 30) // 设置30秒超时
public void complexTask() {
    // ... 复杂业务逻辑
}

rollbackFor:Spring默认只在抛出 RuntimeException 及其子类时才回滚事务,对于 Checked Exception 则不会回滚。为了明确行为,建议显式指定。

@Transactional(rollbackFor = Exception.class) // 明确指定所有异常都回滚
public void createOrder() throws Exception {
    // ... 业务逻辑
}

@Transactional 注解合理地配置这些参数,能够有效避免许多潜在的线上问题。

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

订单创建成功后,通常需要发送一条MQ消息(例如,用于30分钟后自动取消未支付的订单)。我之前是这么写的:

@Transactional
public void createOrder() {
    orderMapper.insert(order);

    // 直接发送MQ消息
    mqSender.send("order.cancel.delay", order.getId());
}

这段代码看起来没问题,但实际上存在一个致命缺陷。

问题出在执行时机上

问题本质:MQ消息已经成功发送出去了,但此时数据库事务可能因为后续操作失败而回滚,导致订单根本不存在。30分钟后,消息消费者尝试取消订单时,会发现订单不存在,产生数据不一致。

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

  • 订单插入、库存扣减 → 在同一个数据库事务内,遵循ACID原则,要么全部成功,要么全部回滚。
  • MQ消息发送 → 不在数据库事务管理范围内,一旦发出就无法撤回。

使用事务同步器解决此问题

Spring提供了事务生命周期的钩子(TransactionSynchronization),允许我们在事务的特定阶段执行回调。

@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消息不会被发送");
                }
            }
        }
    );
}

核心区别:只有订单数据真正持久化到数据库(事务提交)后,才会发送MQ消息。如果事务回滚,消息则不会发送。

事务同步器的4个生命周期钩子

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);
        }
    }
);

这四个方法的执行顺序是固定的:beforeCommitbeforeCompletionafterCommitafterCompletion

哪些场景必须使用 afterCommit

所有“对外的、具有副作用”的操作,都应该考虑放在 afterCommit 中执行:

场景1:发送MQ消息

@Override
public void afterCommit() {
    // 发送延迟取消订单的消息
    mqSender.send("order.cancel.delay", orderId);
}

场景2:清理Redis缓存

@Override
public void afterCommit() {
    // 清理商品详情缓存
    redisTemplate.delete("product:" + productId);
}

场景3:记录业务日志到另一个数据库

@Override
public void afterCommit() {
    // 写入独立的日志库(不在当前主事务内)
    logMapper.insert(businessLog);
}

场景4:调用外部服务(如短信、推送)

@Override
public void afterCommit() {
    // 通知第三方系统订单创建成功
    thirdPartyService.notify(order);
}

核心原则:确保只有业务数据真正持久化后,才让外部系统感知到状态变化。

同库的日志记录也需要用 afterCommit 吗?

理论上,如果日志表和订单表在同一个数据库的同一个事务内,即使提前写入,事务回滚时日志也会一起回滚,不会造成数据不一致。

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

  1. 解耦:将订单核心业务与日志记录逻辑分离。
  2. 性能:避免日志操作增加主事务的耗时。
  3. 重试:日志记录失败可以独立重试,不影响主订单业务。

因此,即使同库,也建议将日志记录放在 afterCommit 中异步执行。

我进行的测试输出如下,验证了执行顺序的可靠性:

# 运行测试
POST /synchronization/phases

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

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

订单创建成功后,往往需要完成多个后续操作:发送MQ、记录日志、发送通知等。如果都写在主方法里,代码会变得臃肿:

@Transactional
public void createOrder() {
    orderMapper.insert(order);

    // 业务逻辑越来越多,耦合严重
    mqSender.send(...);
    logService.save(...);
    notifyService.send(...);
}

而且事务范围会被不必要地扩大,难道发短信、发推送也需要在数据库事务里吗?

使用事务事件监听进行解耦

第1步:定义领域事件

@Getter
@AllArgsConstructor
public class OrderCreatedEvent {
    private String orderSn;
    private Long memberId;
    private BigDecimal amount;
}

第2步:在业务方法中发布事件

@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步:编写事件监听器

@Component
public class OrderEventListener {

    // 关键:使用 @TransactionalEventListener 并指定 phase = 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 方法本身与事务无关,但监听器的执行时机取决于其监听方式。

关键区别

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

事务事件监听的4个阶段

// 提交前(可进行最后校验)
@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. 必须在事务方法内发布事件

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

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

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

@Transactional
public void parentMethod() {
    // 父事务逻辑
    childMethod(); // 子事务(REQUIRES_NEW)
}

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

3. 无事务环境下的后备执行

// 默认情况下,如果没有活动事务,AFTER_COMMIT监听器不会执行
// 可以通过 fallbackExecution = true 来改变此行为
@TransactionalEventListener(
    phase = TransactionPhase.AFTER_COMMIT,
    fallbackExecution = true // 即使没有事务也会执行
)
public void handleEvent(OrderCreatedEvent event) {
    // ...
}

与事务同步器的对比

方式 代码耦合度 扩展性 适用场景
TransactionSynchronization 高(在业务方法内部注册) 简单场景,只有1-2个后续操作
@TransactionalEventListener (基于发布-订阅模型) 复杂场景,有多个需要解耦的后续操作

建议

  • 如果只有1-2个简单的后续操作,可以直接使用 TransactionSynchronization
  • 如果后续操作较多,或者未来可能扩展,强烈推荐使用 @TransactionalEventListener 进行解耦。

这样做的好处是业务逻辑清晰,当需要增加新的后续操作(比如新增一个积分发放)时,只需编写一个新的监听器即可,无需修改原有的订单创建方法。

批量操作必须用手动事务

假设需要批量发货100个订单,如果其中第50个订单发货失败,该如何处理?

如果使用 @Transactional 注解:

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

问题在于:100个订单的处理被包裹在同一个事务中,任何一个失败都会导致整个事务回滚,前面已经成功的49个订单也会被撤销。

但实际的业务需求往往是:成功的订单正常发货,失败的订单单独记录下来,进行后续处理。

使用 PlatformTransactionManager 进行手动控制

@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);
    }
}

高级用法:为批处理定制事务属性

对于定时任务、后台批处理等高并发或长时间运行的场景,可以显式控制每个事务的属性,以优化性能。

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个失败。结果是97个成功的订单状态正常更新,3个失败的订单信息被记录下来。

核心价值:实现了每条数据的独立事务控制,部分失败不会影响其他成功的数据,这对于后端开发中的健壮性要求至关重要。

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

创建订单时,通常会调用另一个方法createOrderItems来插入订单商品。如果两个方法都标注了 @Transactional,会发生什么?

Spring定义了7种事务传播行为,但最常用的只有3种:REQUIREDREQUIRES_NEWNESTED

REQUIRED(默认):同成同败

行为:如果当前存在事务,则加入该事务;如果当前没有事务,则新建一个事务。这是最常用的设置。

// 父方法
@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);
}

关键点

  • 父子方法在同一个物理事务中执行。
  • 子方法抛出异常 → 整个事务回滚(父方法的操作也一并回滚)。
  • 订单和订单商品真正做到“同生共死”。

适用场景:同一个业务流程内的多个步骤需要保证原子性,“要么全做,要么全不做”。80%的业务场景都使用这个传播行为。

REQUIRES_NEW:独立事务

行为:挂起当前事务(如果存在),创建一个全新的事务。新事务独立提交或回滚。

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

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

关键点

  • 子方法失败只影响它自己的事务,父事务不受影响。
  • 父事务后续失败回滚,子事务已提交的结果仍然保留
  • 审计日志一定会被保存,即使订单创建最终失败。

适用场景:那些必须独立持久化的操作,即使主业务流程失败也不能丢失。例如:

  • 记录关键审计日志。
  • 写入消息表(用于异步任务)。
  • 保存操作通知记录。

NESTED:嵌套事务与局部回滚

行为:如果当前存在事务,则在当前事务内创建一个“嵌套事务”(通过数据库的保存点-Savepoint实现)。嵌套事务是外层事务的一部分,但可以独立回滚。

// 父方法
@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支持,MyISAM不支持)。

适用场景:主流程必须继续,但某个非核心的子步骤允许“局部失败”。例如:

  • 批量处理中,某一条失败不影响已成功处理的条目。
  • 创建订单赠品、优惠券等可选功能。

注意

  • 需要使用 DataSourceTransactionManager(JPA的某些实现可能不支持)。
  • 数据库存储引擎必须支持保存点。

三种常用传播行为对比

传播行为 事务关系 子方法失败的影响 父方法失败的影响 典型场景
REQUIRED 共享同一事务 整个事务回滚 整个事务回滚 订单 + 订单商品
REQUIRES_NEW 独立新事务 只回滚子事务 子事务已提交,不受影响 审计日志、操作记录
NESTED 嵌套事务(保存点) 回滚到保存点 整个事务回滚(含子操作) 赠品、优惠券等可选功能

选型建议

  • 默认使用 REQUIRED:绝大多数需要事务原子性的场景。
  • 需要独立落盘的用 REQUIRES_NEW:如审计日志、必须记录的消息。
  • 需要局部回滚的用 NESTED:如批处理中的单条记录、非核心的可选功能。

在我的测试项目中,这三种传播行为均按预期工作。理解并正确运用事务管理的传播机制,是构建健壮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

// 性能差:循环单次插入
@Transactional
public void save(List<Item> items) {
    for (Item item : items) {
        mapper.insert(item); // N次数据库网络IO
    }
}

// 性能优:批量插入
@Transactional
public void save(List<Item> items) {
    mapper.batchInsert(items); // 1次数据库网络IO
}

我曾忽略这一点,在处理1000条数据时,循环插入耗时约10秒,而改为批量插入后仅需0.5秒,性能差异巨大。这在处理MySQL等数据库时尤为重要。

长事务必须设置超时

@Transactional(timeout = 30)
public void longTask() {
    // 复杂的长耗时业务逻辑
}

在生产环境中,为可能存在长事务的方法设置超时是必须的,可以防止某个事务长时间持有数据库锁,导致系统雪崩。

总结一下

这6种进阶的事务控制方案,各自针对特定的业务痛点:

  1. 编程式事务 → 解决如“库存不足时保留订单”这类需区分业务异常与系统故障的场景。
  2. @Transactional参数 → 明确隔离级别、超时、回滚规则,提升代码的健壮性与可移植性。
  3. 事务同步器 → 确保只有在事务提交成功后,才执行如发送MQ消息等副作用操作。
  4. 事务事件监听 → 通过发布-订阅模式,解耦核心业务逻辑与后续处理,提升代码可维护性。
  5. 手动控制事务 → 实现批量操作中“部分成功、部分失败”的精细化控制。
  6. 事务传播机制 → 灵活管理多个事务方法之间的边界,应对日志记录、嵌套操作等复杂场景。

对于80%的常规业务场景,简单的 @Transactional 注解足以应对。当遇到上述特殊需求时,再根据具体情况选择对应的高级方案即可。技术选型的核心是“够用就好”,避免过度设计。

代码在这里

完整的可运行示例代码与测试用例:
https://gitee.com/sh_wangwanbao/simple-transactional
数据库初始化脚本位于 doc/simple-transactional-init.sql,导入后即可运行所有测试。




上一篇:深入解析ELF调试核心:.debug_info节与DWARF格式详解
下一篇:Vue3主题切换工程实践:基于CSS变量与Class覆盖实现多主题与明暗模式
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-1-18 16:29 , Processed in 0.318422 second(s), 38 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表