【异步事件ApplicationEventPublisher及精细化事务处理TransactionTemplate 】

ApplicationEventPublisher @EventListener @TransactionalEventListener

在Spring框架中,事件发布和监听机制基于ApplicationEventPublisherApplicationListener(或注解驱动的监听器)。当您使用eventPublisher.publishEvent()发布事件时,事件会被发送到所有匹配的监听器,这些监听器通过实现ApplicationListener接口或使用@EventListener@TransactionalEventListener注解来定义。

  • 事件发布eventPublisher.publishEvent(ChangeClassEvent.builder()...build())发布了一个ChangeClassEvent事件。
  • 事件监听doHandle方法使用@TransactionalEventListener监听该事件,并在事务提交后执行。

因此,当eventPublisher.publishEvent被调用时,事件会被发送到doHandle方法进行处理。如果您有其他监听器也监听ChangeClassEvent,它们也会被触发。

注意事项

  • 确保事件监听器(如ChangeClassEventListener或包含doHandle的类)被Spring管理(例如,使用@Component注解)。
  • 如果有多个监听器,执行顺序可能不确定,但可以通过@Order注解指定顺序。
  • 使用@TransactionalEventListener时,监听器只在事务提交后触发,如果事件发布不在事务中,需要配置fallbackExecution = true
  1. 业务在一个事务里做完数据修改后,通过 eventPublisher.publishEvent(...)ChangeEvent 发布出去。

  2. 由于事务尚未提交,事件被 Spring 的 ApplicationEventMulticaster 缓存起来。

  3. 当事务提交(AFTER_COMMIT)以后,Spring 会调用所有标记了 @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) 的方法,把事件投递给它。

  4. doHandle 收到事件后,在新的 独立事务里(通过 TransactionTemplate)依次完成:

    • 发消息通知
    • 初始化业务周
    • 初始化业务周期
    • 更新预排期
    • 刷新带班信息
    • 绑定微信社群

如何知道事件"发到哪里去了"

在 Spring 中,事件机制本质上是**"发布-订阅"**模型:

  • 发布者只负责把事件丢到 ApplicationContext
  • Spring 在容器启动时会扫描所有带 @EventListener / @TransactionalEventListener 的方法,把它们注册成监听器。
  • 运行时根据事件类型和方法参数匹配,把事件派发给匹配的监听器。

简单示例

以下是一个简单的示例,展示如何发布事件和监听事件:

1. 定义事件类

事件类可以是任意POJO,不需要继承特定类(Spring 4.2+支持)。

java

kotlin 复制代码
import lombok.Builder;
import lombok.Data;

@Data
@Builder
public class ChangeEvent {
    private Long Id;
    private Boolean isCreate;
    // 其他字段...
}
2. 发布事件

在服务中注入ApplicationEventPublisher并发布事件。

java

kotlin 复制代码
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;

@Service
public class CService {
    @Resource
    private ApplicationEventPublisher eventPublisher;

    public void updateClass(Long Id, Boolean isCreate) {
        // 构建事件对象
        ChangeClassEvent event = ChangeEvent.builder()
                .Id(Id)
                .isCreate(isCreate)
                .build();
        // 发布事件
        eventPublisher.publishEvent(event);
    }
}
3. 监听事件

使用@TransactionalEventListener监听事件,并在事务提交后处理。

java

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

@Component
public class ChangeEventListener {

    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleChangeClassEvent(ChangeEvent event) {
        System.out.println("Received event: " + event);
        // 这里处理事件逻辑
    }
}
typescript 复制代码
@SpringBootApplication
public class EventDemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(EventDemoApplication.class, args);
    }

    @RestController
    static class DemoController {
        @Autowired
        private ApplicationEventPublisher publisher;

        @GetMapping("/change")
        public String change() {
            publisher.publishEvent(new ChangeEvent(1L));
            return "sent";
        }
    }

    /* 事件对象 */
    @Data
    @AllArgsConstructor
    public static class ChangeEvent {
        private Long Id;
    }

    /* 监听器 */
    @Component
    static class ChangeEventHandler {
        @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
        public void onChange(ChangeEvent event) {
            System.out.println("收到事件 -> " + event.getId());
        }
    }
}

TransactionTemplate

  1. TransactionTemplate 是 Spring 提供的编程式事务 工具,用来在代码里手动控制事务的边界。相比 @Transactional 注解,它更灵活,适合在非 public 方法、循环内部、或需要细粒度控制的场景。
  2. 依赖(已有 Spring Boot 则自带)

核心概念

1. 事务基础

事务是数据库操作的基本单元,具有 ACID 特性:

  • 原子性 (Atomicity):事务中的所有操作要么全部完成,要么全部不完成
  • 一致性 (Consistency):事务执行前后数据库状态保持一致
  • 隔离性 (Isolation):并发事务之间互不干扰
  • 持久性 (Durability):事务提交后结果永久保存

2. TransactionTemplate 优势

  • 代码中明确显示事务边界
  • 可以更精细地控制事务行为(回滚条件、超时设置等)
  • 避免声明式事务可能带来的代理问题

3. 注入并使用 TransactionTemplate

在服务类中注入并使用 TransactionTemplate:

java 复制代码
@Service
public class UserService {
    
    @Resource
    private TransactionTemplate transactionTemplate;
    
    @Resource
    private UserRepository userRepository;
    
    public void createUser(final User user) {
        // 使用 executeWithoutResult 执行无返回值的事务操作
        transactionTemplate.executeWithoutResult(status -> {
            try {
                userRepository.save(user);
                // 可以执行其他数据库操作
                // 如果出现异常,事务会自动回滚
            } catch (Exception e) {
                // 如果需要,可以手动设置回滚
                status.setRollbackOnly();
                throw new RuntimeException("创建用户失败", e);
            }
        });
    }
    
    public User createUserWithResult(final User user) {
        // 使用 execute 执行有返回值的事务操作
        return transactionTemplate.execute(status -> {
            try {
                User savedUser = userRepository.save(user);
                // 其他数据库操作...
                return savedUser;
            } catch (Exception e) {
                status.setRollbackOnly();
                throw new RuntimeException("创建用户失败", e);
            }
        });
    }
}

常用配置选项

1. 传播行为 (Propagation Behavior)

定义事务如何传播:

  • PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果不存在,则创建一个新事务(默认)
  • PROPAGATION_REQUIRES_NEW:创建一个新事务,如果当前存在事务,则挂起当前事务
  • PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果不存在,则以非事务方式执行
  • 其他选项:PROPAGATION_MANDATORY, PROPAGATION_NOT_SUPPORTED, PROPAGATION_NEVER, PROPAGATION_NESTED

设置方式:

ini 复制代码
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);

2. 隔离级别 (Isolation Level)

定义事务的隔离程度:

  • ISOLATION_READ_UNCOMMITTED:读未提交
  • ISOLATION_READ_COMMITTED:读已提交(常用)
  • ISOLATION_REPEATABLE_READ:可重复读
  • ISOLATION_SERIALIZABLE:串行化

设置方式:

ini 复制代码
transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);

3. 超时设置 (Timeout)

定义事务超时时间(秒):

javascript 复制代码
transactionTemplate.setTimeout(30); // 30秒

4. 只读事务 (Read-only)

标记事务为只读,优化性能:

arduino 复制代码
transactionTemplate.setReadOnly(true);

实际应用示例

1. 多步骤操作的事务管理

typescript 复制代码
public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
    transactionTemplate.executeWithoutResult(status -> {
        try {
            // 第一步:从源账户扣款
            accountRepository.debit(fromAccountId, amount);
            
            // 第二步:向目标账户存款
            accountRepository.credit(toAccountId, amount);
            
            // 第三步:记录交易日志
            transactionLogRepository.logTransfer(fromAccountId, toAccountId, amount);
            
        } catch (Exception e) {
            status.setRollbackOnly();
            throw new RuntimeException("转账失败", e);
        }
    });
}

2. 有条件的事务回滚

typescript 复制代码
public void processOrder(Order order) {
    transactionTemplate.executeWithoutResult(status -> {
        try {
            // 库存检查
            if (!inventoryService.hasSufficientStock(order)) {
                status.setRollbackOnly();
                throw new InsufficientStockException("库存不足");
            }
            
            // 扣减库存
            inventoryService.reduceStock(order);
            
            // 创建订单
            orderRepository.save(order);
            
        } catch (InsufficientStockException e) {
            // 已设置回滚,只需重新抛出异常
            throw e;
        } catch (Exception e) {
            status.setRollbackOnly();
            throw new RuntimeException("订单处理失败", e);
        }
    });
}
xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>  <!-- 或 spring-boot-starter-data-jpa -->
</dependency>
  1. 配置一个 Bean(Spring Boot 会自动配好,可直接注入)
typescript 复制代码
@Configuration
public class TxConfig {
    @Bean
    public TransactionTemplate transactionTemplate(PlatformTransactionManager txManager) {
        TransactionTemplate tt = new TransactionTemplate(txManager);
        // 可选:统一设置传播行为、隔离级别、超时
        tt.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
        tt.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        tt.setTimeout(10);  // 秒
        return tt;
    }
}
  1. 基本用法
    4.1 无返回值:executeWithoutResult
typescript 复制代码
@Service
public class UserService {
    @Resource
    private TransactionTemplate transactionTemplate;
    @Resource
    private JdbcTemplate jdbcTemplate;

    public void createUser(String name) {
        transactionTemplate.executeWithoutResult(status -> {
            // 这里就是一个事务
            jdbcTemplate.update("insert into user(name) values (?)", name);

            // 手动回滚示例
            if ("error".equals(name)) {
                status.setRollbackOnly();
            }
        }); // 出块即提交或回滚
    }
}

4.2 有返回值:execute

kotlin 复制代码
public Long countUser() {
    return transactionTemplate.execute(status ->
        jdbcTemplate.queryForObject("select count(*) from user", Long.class)
    );
}
  1. 常见回调接口
  • TransactionCallback<T>:有返回值
  • TransactionCallbackWithoutResult:无返回值(executeWithoutResult 的底层)
  1. 与声明式事务对比
    | 场景 | @Transactional | TransactionTemplate | |---------------|----------------|---------------------| | 代码侵入性 | 低(注解) | 高(需写代码块) | | 灵活度 | 低 | 高(可循环/条件) | | 回滚方式 | 抛异常 | status.setRollbackOnly() | | 适用位置 | public 方法 | 任意位置 |
  2. 小结
  • 需要简单、快速 地包裹一段代码为事务 → 用 TransactionTemplate
  • 需要全局、统一 的事务策略 → 用 @Transactional
相关推荐
IT_陈寒1 天前
Spring Boot 3 + GraalVM:5个实战技巧让Java应用启动速度提升300%
前端·人工智能·后端
无奈何杨1 天前
CoolGuard风控系统配置评分卡、权重策略|QLExpress脚本
前端·后端
陈随易1 天前
改变世界的编程语言MoonBit:项目文件详解
前端·后端·程序员
用户6120414922131 天前
C语言做的城市天气数据管理与统计
c语言·后端·敏捷开发
ursazoo1 天前
记一次线上API调用失败的排查过程:从405到时间同步
后端
聪明墨菲特1 天前
HttpClient工具类优化实践:提升性能与可维护性
后端·设计模式
Java中文社群1 天前
面试官:如何提升项目并发性能?
java·后端·面试
阿杆1 天前
OAuth 图解指南(阮老师推荐)
前端·后端·架构
Mintopia1 天前
每个国家的核安全是怎么保证的,都不怕在自己的领土爆炸吗?
前端·后端·面试
tonydf1 天前
浅聊一下AOP
后端