ApplicationEventPublisher @EventListener @TransactionalEventListener
在Spring框架中,事件发布和监听机制基于ApplicationEventPublisher
和ApplicationListener
(或注解驱动的监听器)。当您使用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
。
-
业务在一个事务里做完数据修改后,通过
eventPublisher.publishEvent(...)
把ChangeEvent
发布出去。 -
由于事务尚未提交,事件被 Spring 的
ApplicationEventMulticaster
缓存起来。 -
当事务提交(
AFTER_COMMIT
)以后,Spring 会调用所有标记了@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
的方法,把事件投递给它。 -
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
TransactionTemplate
是 Spring 提供的编程式事务 工具,用来在代码里手动控制事务的边界。相比@Transactional
注解,它更灵活,适合在非 public 方法、循环内部、或需要细粒度控制的场景。- 依赖(已有 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>
- 配置一个 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;
}
}
- 基本用法
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)
);
}
- 常见回调接口
TransactionCallback<T>
:有返回值TransactionCallbackWithoutResult
:无返回值(executeWithoutResult
的底层)
- 与声明式事务对比
| 场景 | @Transactional | TransactionTemplate | |---------------|----------------|---------------------| | 代码侵入性 | 低(注解) | 高(需写代码块) | | 灵活度 | 低 | 高(可循环/条件) | | 回滚方式 | 抛异常 |status.setRollbackOnly()
| | 适用位置 | public 方法 | 任意位置 | - 小结
- 需要简单、快速 地包裹一段代码为事务 → 用
TransactionTemplate
- 需要全局、统一 的事务策略 → 用
@Transactional