TransactionalEventListener
一、遇到业务问题
在线课业务某一场景:需要创建班级后对接第三方平台,步骤是:
- 完成自己方业务数据落库
- 同步三方平台数据
- 同步成功后维护自己方数据和三方关联关系
由于链路业务较长决定实现方案是第一步执行完后同步返回web页面结果,默认同步三方数据一定成功(后话:不成功会补偿);异步执行同步数据并维护关联关系。
二、设计方案
使用线程池去处理 2,3 两步。(队列执行一样)
缺陷:在业务实际运行过程中会发现,第一步事务还未提交,第二步已经执行,会出现幻读情况。
临时解决方案:Thread.sleep(3000) (延迟队列,效果一样)。
最终方案:决定第一步事务提交后再去操作 2,3。
三、技术实现
TransactionalEventListener:事务监听器;其实就是 EventListener 事件监听器的一种。
ApplicationEventPublisher:事件发布器。
ApplicationEvent:事件。
- 创建一个任务事件
/**
* @Description
* @Author wuxb
* @Date 2022/8/23 17:07
*/
public class TaskEvent extends ApplicationEvent {
public TaskEvent(TaskRun source) {
super(source);
}
@Override
public TaskRun getSource() {
return (TaskRun) super.getSource();
}
@FunctionalInterface
public interface TaskRun {
void run();
}
}
说明
@FunctionalInterface
为了下游触发任务便于使用了lambda表达式。
- 创建指定事件监听
@Component
public class TaskListener {
@Async("taskExecutorService")
@Transactional(propagation = Propagation.REQUIRES_NEW rollbackFor = Exception.class)
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void taskHandler(TaskEvent taskEvent) {
System.out.println("=============> start taskHandler:" + Thread.currentThread().getId() + ", name : " + Thread.currentThread().getName());
if (Objects.nonNull(taskEvent)) {
taskEvent.getSource().run();
}
}
}
说明:
@Transactional(propagation = Propagation.REQUIRES_NEW rollbackFor = Exception.class)
新开启一个使用用在此监听器上(异步业务也分1,2,3链路,3执行溢出,1,2也需要回滚)
@Async("taskExecutorService")
ApplicationEventPublisher默认是同步的,需要开启异步操作;而我这里使用的是线程池,
所以这边又创建了一个线程池的bean
/**
* @Description
* @Author wuxb
* @Date 2022/8/24 11:36
*/
@Configuration
@EnableAsync
public class TaskExecutorConfig {
@Bean
public ExecutorService taskExecutorService() {
return new ExecutorContextProxy(ThreadPool.newCachedThreadPool("BaseClassInTaskService", 20, 1000, 10000));
}
}
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
这个就是重点,监听事务提交后触发;可以指定事务场景:
public enum TransactionPhase {
BEFORE_COMMIT,
AFTER_COMMIT,
AFTER_ROLLBACK,
AFTER_COMPLETION;
private TransactionPhase() {
}
}
- 执行
在需要的地方加入一行代码即可
@Autowired
private ApplicationEventPublisher applicationEventPublisher;
// 执行任务
applicationEventPublisher.publishEvent(new TaskEvent(() -> {
run(param.getMainTaskId());
}));