事务绑定事件监听器的使用

@TransactionalEventListener 完整使用示例(事务绑定事件监听)

@TransactionalEventListener 是 Spring 提供的事务绑定事件监听器 ,核心作用是「在事务的特定阶段(提交成功/回滚/完成)才执行监听器逻辑」,解决了普通 @EventListener 可能在事务未提交时执行、导致数据不一致的问题(如订单创建事务未提交就扣减库存)。

一、核心原理(先理解再上手)

1. 与普通 @EventListener 的区别

特性 @EventListener @TransactionalEventListener
事务关联 无,立即执行 绑定事务,指定阶段执行
核心场景 无需事务的普通事件 需依赖事务结果的事件(如订单提交后扣库存)
触发时机 事件发布时立即执行 事务提交/回滚/完成后执行

2. 事务阶段(核心参数 phase

@TransactionalEventListener 核心参数 phase 支持 4 个事务阶段,常用 AFTER_COMMIT

阶段常量 触发时机 适用场景
TransactionPhase.AFTER_COMMIT(默认) 事务提交成功后执行 核心业务(扣库存、发通知)
TransactionPhase.AFTER_ROLLBACK 事务回滚后执行 回滚补偿(恢复库存、记录失败日志)
TransactionPhase.AFTER_COMPLETION 事务完成后执行(无论提交/回滚) 通用收尾(清理临时数据)
TransactionPhase.BEFORE_COMMIT 事务提交前执行 提交前校验(如订单金额二次核对)

3. 核心参数

  • phase:指定事务触发阶段(默认 AFTER_COMMIT);
  • fallbackExecution:事务不存在时是否执行(默认 false,即无事务则不执行);
  • classes:监听的事件类型(同 @EventListener)。

二、完整使用示例(订单创建+事务提交后扣库存)

需求场景

  • 订单创建接口包含数据库事务(保存订单数据);
  • 仅当订单事务提交成功后,才触发「扣减库存」逻辑;
  • 若订单事务回滚(如库存不足),则不扣库存,且触发「回滚日志记录」。

步骤1:定义自定义事务事件(承载业务数据)

创建继承 ApplicationEvent 的事件类,封装订单和库存相关数据:

java 复制代码
import org.springframework.context.ApplicationEvent;

/**
 * 订单创建事务事件(绑定订单事务)
 */
public class OrderCreateTransactionalEvent extends ApplicationEvent {
    // 业务数据:订单ID、商品ID、扣减数量
    private Long orderId;
    private Long productId;
    private Integer deductNum;

    // 构造方法(source为事件源,通常是发布事件的service)
    public OrderCreateTransactionalEvent(Object source, Long orderId, Long productId, Integer deductNum) {
        super(source);
        this.orderId = orderId;
        this.productId = productId;
        this.deductNum = deductNum;
    }

    // getter
    public Long getOrderId() { return orderId; }
    public Long getProductId() { return productId; }
    public Integer getDeductNum() { return deductNum; }
}

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

创建订单 Service,在带事务的方法中发布事件(关键:事件必须在事务内发布):

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

/**
 * 订单服务(核心:@Transactional 注解保证事务)
 */
@Service
public class OrderService {
    @Autowired
    private ApplicationEventPublisher eventPublisher; // 事件发布器

    /**
     * 创建订单(带事务)
     * @Transactional :开启事务,异常则回滚
     */
    @Transactional(rollbackFor = Exception.class)
    public void createOrder(Long orderId, Long productId, Integer deductNum) {
        try {
            // 1. 模拟订单入库(核心业务,包含数据库操作)
            System.out.println("【订单事务】保存订单数据:orderId=" + orderId);
            
            // 2. 模拟业务校验(如库存不足则抛异常,触发事务回滚)
            if (deductNum > 10) { // 假设单次最多扣10个库存
                throw new RuntimeException("库存扣减数量超过限制,事务回滚");
            }

            // 3. 发布事务事件(必须在事务方法内发布)
            OrderCreateTransactionalEvent event = new OrderCreateTransactionalEvent(
                    this, orderId, productId, deductNum);
            eventPublisher.publishEvent(event);

            System.out.println("【订单事务】订单创建完成,等待事务提交");
        } catch (Exception e) {
            System.out.println("【订单事务】异常,事务回滚:" + e.getMessage());
            throw e; // 抛出异常触发事务回滚
        }
    }
}

步骤3:实现 @TransactionalEventListener 监听器

创建 3 个监听器,分别监听「事务提交后、回滚后、完成后」阶段:

java 复制代码
import org.springframework.context.event.TransactionalEventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;

/**
 * 订单事务事件监听器(绑定事务阶段)
 */
@Component
public class OrderTransactionalListener {

    /**
     * 监听器1:事务提交成功后执行(核心:扣减库存)
     * phase = AFTER_COMMIT:默认值,可省略
     */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderCommit(OrderCreateTransactionalEvent event) {
        // 实际业务:扣减商品库存(仅当订单事务提交成功才执行)
        System.out.println("【事务提交后】执行扣减库存:orderId=" + event.getOrderId()
                + ",productId=" + event.getProductId()
                + ",扣减数量=" + event.getDeductNum());
        // stockService.deductStock(event.getProductId(), event.getDeductNum());
    }

    /**
     * 监听器2:事务回滚后执行(补偿逻辑:记录回滚日志)
     */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleOrderRollback(OrderCreateTransactionalEvent event) {
        System.out.println("【事务回滚后】执行补偿逻辑:orderId=" + event.getOrderId()
                + ",记录回滚日志,恢复库存");
        // logService.recordRollbackLog(event.getOrderId());
    }

    /**
     * 监听器3:事务完成后执行(无论提交/回滚,通用收尾)
     */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    public void handleOrderCompletion(OrderCreateTransactionalEvent event) {
        System.out.println("【事务完成后】执行通用收尾:orderId=" + event.getOrderId()
                + ",清理临时数据");
    }
}

步骤4:测试验证(分两种场景)

创建测试控制器/测试类,验证「事务提交」和「事务回滚」两种场景:

java 复制代码
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;

/**
 * 测试控制器
 */
@RestController
public class OrderTestController {
    @Autowired
    private OrderService orderService;

    /**
     * 场景1:事务提交成功(扣减数量=5 ≤ 10)
     * 访问:http://localhost:8080/createOrder/1001/1/5
     */
    @GetMapping("/createOrder/{orderId}/{productId}/{deductNum}")
    public String createOrderSuccess(
            @PathVariable Long orderId,
            @PathVariable Long productId,
            @PathVariable Integer deductNum) {
        try {
            orderService.createOrder(orderId, productId, deductNum);
            return "订单创建成功,事务已提交";
        } catch (Exception e) {
            return "订单创建失败,事务已回滚:" + e.getMessage();
        }
    }
}
场景1:事务提交成功(deductNum=5)

访问 http://localhost:8080/createOrder/1001/1/5,控制台输出:

复制代码
【订单事务】保存订单数据:orderId=1001
【订单事务】订单创建完成,等待事务提交
【事务完成后】执行通用收尾:orderId=1001,清理临时数据
【事务提交后】执行扣减库存:orderId=1001,productId=1,扣减数量=5

✅ 关键:仅执行「提交后」和「完成后」监听器,「回滚后」监听器不执行。

场景2:事务回滚(deductNum=15)

访问 http://localhost:8080/createOrder/1002/1/15,控制台输出:

复制代码
【订单事务】保存订单数据:orderId=1002
【订单事务】异常,事务回滚:库存扣减数量超过限制,事务回滚
【事务完成后】执行通用收尾:orderId=1002,清理临时数据
【事务回滚后】执行补偿逻辑:orderId=1002,记录回滚日志,恢复库存

✅ 关键:仅执行「回滚后」和「完成后」监听器,「提交后」监听器不执行。

三、进阶用法(关键参数配置)

1. fallbackExecution:无事务时是否执行

默认 fallbackExecution=false(无事务则监听器不执行),若需「有事务则绑定阶段,无事务则立即执行」,设置为 true

java 复制代码
/**
 * 无事务时也执行(fallbackExecution = true)
 */
@TransactionalEventListener(fallbackExecution = true)
public void handleOrderWithFallback(OrderCreateTransactionalEvent event) {
    System.out.println("【无事务也执行】处理订单:" + event.getOrderId());
}

2. 监听多个事件类型(classes 参数)

java 复制代码
/**
 * 监听多个事务事件类型
 */
@TransactionalEventListener(
        classes = {OrderCreateTransactionalEvent.class, OrderCancelTransactionalEvent.class},
        phase = TransactionPhase.AFTER_COMMIT
)
public void handleMultiEvent(ApplicationEvent event) {
    if (event instanceof OrderCreateTransactionalEvent) {
        System.out.println("处理订单创建事件");
    } else if (event instanceof OrderCancelTransactionalEvent) {
        System.out.println("处理订单取消事件");
    }
}

3. 异步执行事务监听器

结合 @Async 实现异步(需先加 @EnableAsync),避免阻塞事务提交:

java 复制代码
import org.springframework.scheduling.annotation.Async;

@Component
public class OrderAsyncTransactionalListener {
    /**
     * 异步执行事务提交后逻辑(不阻塞主线程)
     */
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderCommitAsync(OrderCreateTransactionalEvent event) {
        try {
            Thread.sleep(2000); // 模拟耗时操作(如发送短信)
            System.out.println("【异步+事务提交后】扣减库存:" + event.getOrderId());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

四、避坑要点(高频问题)

1. 监听器不触发(最常见)

  • 原因1:事件未在事务方法内 发布(@Transactional 注解缺失或失效);
    ✅ 解决:确保发布事件的方法加 @Transactional,且事务生效(如非内部调用)。
  • 原因2:fallbackExecution=false 但无事务(监听器直接跳过);
    ✅ 解决:要么保证事务存在,要么设置 fallbackExecution=true
  • 原因3:监听器类未加 @Component(未交给 Spring 容器管理);
    ✅ 解决:添加 @Component@Service 注解。

2. 事务提交前监听器执行后,事务仍回滚

  • 原因:BEFORE_COMMIT 阶段执行后,事务后续逻辑抛出异常;
    ✅ 解决:BEFORE_COMMIT 仅用于「校验」,不执行核心业务(如扣库存),核心业务放 AFTER_COMMIT

3. 异步事务监听器获取不到事务上下文

  • 原因:异步线程与原事务线程隔离,无法获取事务信息;
    ✅ 解决:异步监听器仅用于「无需事务上下文」的操作(如发通知),需事务上下文则同步执行。

4. 事务事件发布后,事务立即提交(无延迟)

  • 现象:监听器执行时,事务已提交,无法获取未提交的脏数据;
    ✅ 解决:这是正常设计,@TransactionalEventListener 本身就是为了事务提交后执行,若需操作未提交数据,用普通 @EventListener

五、核心总结

  1. 核心用途@TransactionalEventListener 用于「依赖事务结果」的场景,避免数据不一致;
  2. 关键配置
    • 优先使用 phase = AFTER_COMMIT(事务提交后执行核心业务);
    • fallbackExecution 控制无事务时是否执行;
    • 结合 @Async 实现异步,避免阻塞主线程;
  3. 避坑关键
    • 事件必须在 @Transactional 方法内发布;
    • 监听器类需交给 Spring 容器管理;
    • 不同事务阶段对应不同业务场景(提交后执行业务,回滚后执行补偿)。

完整代码结构(参考)

复制代码
├── com.example.demo
│   ├── event/OrderCreateTransactionalEvent.java  // 自定义事务事件
│   ├── listener/OrderTransactionalListener.java  // 事务监听器
│   ├── service/OrderService.java                 // 订单服务(发布事件+事务)
│   ├── controller/OrderTestController.java       // 测试控制器
│   └── DemoApplication.java                      // 主类(加 @EnableAsync 开启异步)
相关推荐
我真会写代码1 小时前
深入理解Java JVM:架构、核心机制与实战调优指南
java·jvm·架构
6+h1 小时前
【java IO】BIO、NIO、AIO 全面对比
java·python·nio
骇客野人1 小时前
Java springboot里注解大全和使用指南
java·开发语言·spring boot
用户8307196840821 小时前
Spring Boot 启动报错:OpenFeign 隐性循环依赖,排查了整整一下午
java·spring boot·spring cloud
星辰_mya2 小时前
@SpringBootApplication 与 SPI 机制的终极解密
java·spring boot·spring
xdl25992 小时前
【异常解决】Unable to start embedded Tomcat Nacos 启动报错
java·tomcat
是2的10次方啊2 小时前
串行与并行:高并发系统里的优雅接口设计
java
qiuyuyiyang2 小时前
SpringBoot中如何手动开启事务
java·spring boot·spring
sheji34162 小时前
【开题答辩全过程】以 摩托车及配件售后管系统为例,包含答辩的问题和答案
java