乐途乐途
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
主页
  • 计算机基础

    • TCP/IP协议
    • Linux命令
    • HTTP协议
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C语言
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • MyBatis
    • Spring
    • Spring MVC
    • Maven 入门
    • Maven 进阶
    • Java 设计模式
  • 缓存

    • Redis
联系
阿里云
  • 学习路径
  • 第1章 Spring概述与IoC容器

    • Spring概述与IoC容器
    • Spring Framework 概述
    • IoC 与 DI 核心概念
    • @Configuration 详解
    • @Component 详解
    • @ComponentScan 详解
    • @Import 详解
    • @Profile 详解
    • @PropertySource 详解
    • @Service 详解
    • @Repository 详解
  • 第2章 Bean的定义与依赖注入

    • Bean的定义与依赖注入
    • @Bean 详解
    • @Autowired 详解
    • @Qualifier 详解
    • @Primary 详解
    • @Resource 详解
    • @Inject 详解
    • @Named 详解
    • @Value 详解
    • @Scope 详解
    • @Lazy 详解
  • 第3章 Bean生命周期与作用域

    • Bean生命周期与作用域
    • Bean生命周期概述
    • @PostConstruct
    • @PreDestroy
    • InitializingBean
    • DisposableBean
    • BeanPostProcessor
    • BeanFactoryPostProcessor
  • 第4章 AOP面向切面编程

    • AOP面向切面编程
    • AOP核心概念
    • @EnableAspectJAutoProxy
    • @Aspect
    • @Pointcut
    • @Before
    • @After
    • @AfterReturning
    • @AfterThrowing
    • @Around
  • 第5章 数据访问与事务管理

    • 数据访问与事务管理
    • 数据访问概述
    • @EnableTransactionManagement
    • @Transactional
    • @Transactional 的传播行为
    • @Transactional 的隔离级别
    • @Transactional 的回滚规则
    • @Transactional 的超时与只读属性
    • @TransactionalEventListener
  • 第6章 Spring Boot自动配置基础

    • Spring Boot自动配置基础
    • @SpringBootApplication 注解
    • @EnableAutoConfiguration 注解
    • @ConfigurationProperties 注解
    • @ConditionalOnClass 注解
    • @ConditionalOnMissingBean 注解
    • @ConditionalOnProperty 注解
  • 第7章 从容器到Web: Spring MVC导引

    • Spring MVC 导引
  • 第8章 扩展阅读

    • 扩展阅读
    • Spring 事件机制 — ApplicationEvent / ApplicationListener
    • @EventListener
    • SpEL — Spring 表达式语言
    • 校验 Validation — JSR-303 / JSR-380 Bean Validation
    • 类型转换与数据绑定 — Converter / DataBinder
  • 附录

    • Spring Framework 专业术语
    • Spring 核心知识点
    • Spring 面试高频考点
    • Spring 核心注解速查表

@TransactionalEventListener

定义与作用

@TransactionalEventListener 是 Spring 4.2 引入的事件监听注解,用于将事件监听器的执行绑定到事务的特定阶段。与普通的 @EventListener 不同,它不会立即处理事件,而是延迟到事务进入特定状态(如提交后、回滚后)时才触发。

在企业级应用中,这一机制解决了经典的**"事务与副作用的顺序问题"**:例如订单创建成功后需要发送邮件通知,如果邮件发送在事务提交前执行,而后续数据库操作失败导致回滚,就会出现"订单未创建但邮件已发送"的不一致状态。@TransactionalEventListener 确保副作用操作(发送邮件、更新缓存、同步搜索引擎)只在事务成功提交后才执行。

适用位置与常用属性

适用位置

@TransactionalEventListener 只能标注在方法上,且该方法必须位于 Spring 管理的 Bean 中(通常与 @Component、@Service 等组合使用)。

常用属性

属性类型默认值说明
phaseTransactionPhaseAFTER_COMMIT事件触发的事务阶段
fallbackExecutionbooleanfalse无事务上下文时是否执行
classes / valueClass[]无监听的事件类型

五种事务阶段

阶段触发时机典型用途
BEFORE_COMMIT事务提交前最后的校验、补充操作(如审计字段填充)
AFTER_COMMIT(默认)事务成功提交后发送通知、更新缓存、同步搜索引擎
AFTER_ROLLBACK事务回滚后记录失败日志、发送告警、触发补偿流程
AFTER_COMPLETION事务完成(提交或回滚)后资源清理、连接释放后的收尾工作
默认(无事务)无事务上下文时由 fallbackExecution 控制

配置示例:

@Component
public class OrderEventListener {

    // 事务提交成功后发送邮件
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleOrderCreated(OrderCreatedEvent event) {
        emailService.sendOrderConfirmation(event.getOrderId());
    }

    // 事务回滚后记录失败
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleOrderFailed(OrderCreatedEvent event) {
        alertService.notify("订单创建失败,ID:" + event.getOrderId());
    }
}

核心原理:事件发布与事务阶段的绑定

事务阶段与事件处理时序

内部实现机制

关键理解:@TransactionalEventListener 的事件不是立即分发的,而是被注册到 TransactionSynchronizationManager 的同步钩子中。当事务进入对应阶段时,Spring 遍历已注册的事件监听器,按 phase 匹配后执行。如果当前线程无事务且 fallbackExecution = false,事件会被静默丢弃。

完整示例:飞翔科技订单通知系统

场景简述

飞翔科技公司的电商平台在订单创建后需要执行一系列后续操作:发送确认邮件给用户、更新 Redis 缓存、同步订单到 Elasticsearch。这些操作必须在订单事务成功提交后执行,如果订单回滚则不应触发。小崔(后端开发)使用 @TransactionalEventListener 实现这一需求。

操作前:数据库表结构与初始数据

-- 订单表
CREATE TABLE customer_order (
    order_id BIGINT PRIMARY KEY AUTO_INCREMENT,
    user_id INT NOT NULL,
    product_name VARCHAR(100),
    amount DECIMAL(10,2),
    status VARCHAR(20),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 通知日志表(记录邮件发送状态)
CREATE TABLE notification_log (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id BIGINT,
    notify_type VARCHAR(20),
    status VARCHAR(20),
    sent_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

事件类定义

package com.feixiang.event;

import org.springframework.context.ApplicationEvent;

// 订单创建事件
public class OrderCreatedEvent extends ApplicationEvent {
    private final Long orderId;
    private final int userId;
    private final String productName;
    private final double amount;

    public OrderCreatedEvent(Object source, Long orderId, int userId, String productName, double amount) {
        super(source);
        this.orderId = orderId;
        this.userId = userId;
        this.productName = productName;
        this.amount = amount;
    }

    public Long getOrderId() { return orderId; }
    public int getUserId() { return userId; }
    public String getProductName() { return productName; }
    public double getAmount() { return amount; }
}

Service 层:发布事件

package com.feixiang.service;

import com.feixiang.event.OrderCreatedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
public class OrderService {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    @Transactional
    public Long createOrder(int userId, String productName, double amount) {
        // 1. 插入订单
        jdbcTemplate.update(
            "INSERT INTO customer_order (user_id, product_name, amount, status) VALUES (?, ?, ?, ?)",
            userId, productName, amount, "CREATED"
        );

        // 2. 获取自增主键(简化示例,实际应使用 KeyHolder)
        Long orderId = jdbcTemplate.queryForObject(
            "SELECT LAST_INSERT_ID()", Long.class
        );
        System.out.println("[OrderService] 订单已创建,ID:" + orderId);

        // 3. 发布事件(此时事件不会立即处理,而是等待事务提交)
        eventPublisher.publishEvent(new OrderCreatedEvent(this, orderId, userId, productName, amount));
        System.out.println("[OrderService] 事件已发布,等待事务提交后处理");

        // 4. 模拟业务规则校验(可能触发回滚)
        if (amount > 10000) {
            throw new IllegalStateException("订单金额超限,触发回滚测试");
        }

        // 5. 更新订单状态
        jdbcTemplate.update(
            "UPDATE customer_order SET status = ? WHERE order_id = ?",
            "PAID", orderId
        );
        System.out.println("[OrderService] 订单状态已更新为 PAID");

        return orderId;
    }
}

事件监听器:绑定到事务阶段

package com.feixiang.listener;

import com.feixiang.event.OrderCreatedEvent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;

@Component
public class OrderEventListener {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    /**
     * 事务提交成功后发送确认邮件
     */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void sendConfirmationEmail(OrderCreatedEvent event) {
        System.out.println("[邮件服务] 发送订单确认邮件,订单ID:" + event.getOrderId());
        System.out.println("  → 用户:" + event.getUserId());
        System.out.println("  → 商品:" + event.getProductName());
        System.out.println("  → 金额:" + event.getAmount());

        // 记录通知日志
        jdbcTemplate.update(
            "INSERT INTO notification_log (order_id, notify_type, status) VALUES (?, ?, ?)",
            event.getOrderId(), "EMAIL", "SENT"
        );
        System.out.println("[邮件服务] 通知日志已记录");
    }

    /**
     * 事务提交成功后更新缓存
     */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void updateCache(OrderCreatedEvent event) {
        System.out.println("[缓存服务] 更新订单缓存,订单ID:" + event.getOrderId());
        // 模拟 Redis 操作
        // redisTemplate.opsForValue().set("order:" + event.getOrderId(), ...);
    }

    /**
     * 事务回滚后记录失败日志
     */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleOrderFailure(OrderCreatedEvent event) {
        System.err.println("[告警服务] 订单创建失败,订单ID:" + event.getOrderId());
        System.err.println("  → 原因:事务回滚,可能由于金额超限或库存不足");

        // 记录失败通知(无需事务,因为主事务已回滚)
        jdbcTemplate.update(
            "INSERT INTO notification_log (order_id, notify_type, status) VALUES (?, ?, ?)",
            event.getOrderId(), "ALERT", "FAILED"
        );
    }

    /**
     * 事务完成(无论提交或回滚)后清理临时资源
     */
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMPLETION)
    public void cleanupResources(OrderCreatedEvent event) {
        System.out.println("[资源清理] 订单处理完成,清理临时文件,订单ID:" + event.getOrderId());
    }
}

测试执行与结果

package com.feixiang;

import com.feixiang.service.OrderService;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class TransactionalEventTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx =
            new AnnotationConfigApplicationContext("com.feixiang");
        OrderService orderService = ctx.getBean(OrderService.class);

        // 场景一:正常订单(金额 200,不超限)
        System.out.println("=== 场景一:正常订单 ===");
        try {
            Long orderId = orderService.createOrder(2024001, "机械键盘", 200.00);
            System.out.println("[场景一] 订单创建成功,ID:" + orderId);
        } catch (Exception e) {
            System.err.println("[场景一] 订单创建失败:" + e.getMessage());
        }

        // 场景二:金额超限,触发回滚
        System.out.println("\n=== 场景二:金额超限触发回滚 ===");
        try {
            Long orderId = orderService.createOrder(2024002, "高端服务器", 50000.00);
            System.out.println("[场景二] 订单创建成功,ID:" + orderId);
        } catch (Exception e) {
            System.err.println("[场景二] 订单创建失败:" + e.getMessage());
        }

        ctx.close();
    }
}

操作后:控制台输出与数据变化

场景一(正常订单)控制台输出:

=== 场景一:正常订单 ===
[OrderService] 订单已创建,ID:1
[OrderService] 事件已发布,等待事务提交后处理
[OrderService] 订单状态已更新为 PAID
[邮件服务] 发送订单确认邮件,订单ID:1
  → 用户:2024001
  → 商品:机械键盘
  → 金额:200.0
[邮件服务] 通知日志已记录
[缓存服务] 更新订单缓存,订单ID:1
[资源清理] 订单处理完成,清理临时文件,订单ID:1
[场景一] 订单创建成功,ID:1

关键观察:事件发布语句("事件已发布")在邮件发送("发送订单确认邮件")之前执行,但邮件发送实际发生在事务提交之后。AFTER_COMMIT 阶段的监听器(邮件、缓存)在 AFTER_COMPLETION 阶段的监听器(资源清理)之前执行。

场景一数据库状态:

customer_order 表:

order_iduser_idproduct_nameamountstatus
12024001机械键盘200.00PAID

notification_log 表:

idorder_idnotify_typestatus
11EMAILSENT

场景二(金额超限触发回滚)控制台输出:

=== 场景二:金额超限触发回滚 ===
[OrderService] 订单已创建,ID:2
[OrderService] 事件已发布,等待事务提交后处理
[场景二] 订单创建失败:订单金额超限,触发回滚测试
[告警服务] 订单创建失败,订单ID:2
  → 原因:事务回滚,可能由于金额超限或库存不足
[资源清理] 订单处理完成,清理临时文件,订单ID:2

关键观察:AFTER_COMMIT 阶段的监听器(邮件、缓存)未执行,因为事务已回滚。只有 AFTER_ROLLBACK 和 AFTER_COMPLETION 阶段的监听器被触发。

场景二数据库状态:

customer_order 表:无记录(订单插入被回滚)。

notification_log 表:

idorder_idnotify_typestatus
22ALERTFAILED

注意:AFTER_ROLLBACK 监听器中的 jdbcTemplate.update() 操作不在原事务中执行(原事务已回滚),而是使用独立的新连接执行。这是 Spring 的默认行为,确保监听器操作不会受原事务状态影响。

易错场景与面试考点

反例一:无事务上下文时事件被静默丢弃

@Service
public class OrderService {

    @Autowired
    private ApplicationEventPublisher eventPublisher;

    // 错误:方法无 @Transactional,事件发布后被丢弃
    public void createOrderWithoutTx(int userId, String productName, double amount) {
        // ... 插入订单(无事务)
        eventPublisher.publishEvent(new OrderCreatedEvent(this, ...));
        System.out.println("订单创建完成");
    }
}

@Component
public class OrderEventListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void sendEmail(OrderCreatedEvent event) {
        // 永远不会执行!
        System.out.println("发送邮件");
    }
}

问题分析:createOrderWithoutTx 方法没有 @Transactional,事件发布时当前线程无事务上下文。@TransactionalEventListener 的默认 fallbackExecution = false,事件被静默丢弃,监听器永远不会执行。调用方看到"订单创建完成",但用户永远收不到邮件。

正确写法:

// 方案一:为业务方法添加 @Transactional
@Transactional
public void createOrder(int userId, String productName, double amount) {
    // ...
    eventPublisher.publishEvent(new OrderCreatedEvent(this, ...));
}

// 方案二:设置 fallbackExecution = true(谨慎使用)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true)
public void sendEmail(OrderCreatedEvent event) {
    // 无事务时也会执行
}

反例二:监听器中抛出异常导致其他监听器被跳过

@Component
public class OrderEventListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void sendEmail(OrderCreatedEvent event) {
        throw new RuntimeException("邮件服务器故障");
    }

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void updateCache(OrderCreatedEvent event) {
        // 如果 sendEmail 先执行并抛异常,此方法可能被跳过
        System.out.println("更新缓存");
    }
}

问题分析:同一阶段的多个监听器默认按 Spring 的监听器注册顺序执行。如果前一个监听器抛出异常,异常会向上传播,可能导致后续监听器无法执行(取决于容器的异常处理策略)。邮件发送失败不应影响缓存更新,两者应当是独立的。

正确写法:为每个监听器配置独立的异常处理,或使用异步执行:

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void sendEmail(OrderCreatedEvent event) {
    try {
        emailService.send(event);
    } catch (Exception e) {
        // 记录日志,不抛异常,避免影响其他监听器
        System.err.println("邮件发送失败:" + e.getMessage());
    }
}

反例三:在 AFTER_ROLLBACK 监听器中执行写操作期望参与原事务

@Component
public class OrderEventListener {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void compensateOrder(OrderCreatedEvent event) {
        // 错误期望:此操作与原事务一起回滚
        jdbcTemplate.update("INSERT INTO failed_order (order_id) VALUES (?)", event.getOrderId());
    }
}

问题分析:AFTER_ROLLBACK 监听器执行时,原事务已经回滚并关闭。监听器中的数据库操作使用全新的连接和独立事务(Spring 默认行为)。开发者不应期望这些操作与原事务有任何关联,也不应试图在监听器中"撤销"原事务的操作——原事务已经回滚,数据状态已恢复。

面试高频考点

Q1:@TransactionalEventListener 与 @EventListener 有什么区别?

@EventListener 在事件发布后立即同步执行,不感知事务状态。@TransactionalEventListener 将事件处理延迟到事务的特定阶段(AFTER_COMMIT、AFTER_ROLLBACK 等),确保副作用操作(如发送邮件)只在事务成功提交后执行,避免"通知已发送但数据未保存"的不一致状态。

Q2:fallbackExecution = false 时,无事务上下文发布事件会怎样?

事件会被静默丢弃,监听器不会执行。这是默认行为,因为 AFTER_COMMIT 等阶段在无事务时无意义。如果业务需要在无事务时也执行监听器,需显式设置 fallbackExecution = true,但此时 phase 属性被忽略,监听器立即执行。

Q3:@TransactionalEventListener 的监听器方法需要 @Transactional 吗?

不需要,也不推荐。@TransactionalEventListener 的监听器方法本身不在原事务中执行(原事务已完成),如果添加 @Transactional 会开启新事务。虽然这在某些场景下有用(如监听器中的写操作需要事务保护),但会增加复杂度。建议保持监听器方法简洁,将复杂逻辑委托给专门的 Service 方法。

Q4:同一事件有多个 @TransactionalEventListener,执行顺序如何?

同一阶段的多个监听器按 Spring 的 ApplicationListener 注册顺序执行,通常与 Bean 的初始化顺序和 @Order 注解有关。如果前一个监听器抛出异常,异常会向上传播,可能导致后续监听器被跳过。因此监听器内部应做好异常隔离,避免一个监听器的失败影响其他监听器。

上一页
@Transactional 的超时与只读属性