事务与异步方法(@Async)协同工作
目录
1. 问题场景与风险
(1)典型场景
(2)风险分析
2. 解决方案:事务提交后触发异步操作
(1)代码示例
(2)关键注解
3. 原理解析
(1)事务同步机制
(2)执行流程
4. 优化方案:@TransactionalEventListener
(1)定义事件
(2)发布事件
(3)监听事件
(4)优势
5. 注意事项
(1)事务传播与隔离
(2)异常处理
(3)线程上下文
6. 总结
当异步方法(@Async
)在事务(@Transactional
)上下文中被调用时,事务的提交与异步方法的执行顺序会直接影响数据一致性。
异步方法需要在事务提交后执行。
1. 问题场景与风险
(1)典型场景
- 主方法:包含数据库操作,使用
@Transactional
管理事务。 - 异步方法:在事务提交后执行,如发送通知、更新缓存等。
@Transactional
public void mainMethod() {
//1. 数据库操作(如插入数据)
repository.save(entity);
//2. 调用异步方法
asyncService.sendNotification(entity.getId());//可能未提交事务!
}
(2)风险分析
- 异步方法在事务提交前执行:由于事务提交发生在主方法返回后,异步方法可能读取到未提交的数据,导致脏读或操作失败。
- 数据不一致:若事务回滚,异步方法已执行的操作无法自动回滚。
2. 解决方案:事务提交后触发异步操作
通过 TransactionSynchronizationManager
注册事务同步回调,确保异步方法在事务提交后执行。
(1)代码示例
@Transactional
public void mainMethod() {
// 1. 数据库操作
FileInfo fileInfo = fileRepository.save(new FileInfo());
// 2. 注册事务提交后的回调
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
//事务提交后执行异步方法
fileInfoService.transferFileAsync(fileInfo.getFileId(), webUserDto);
}
}
);
}
(2)关键注解
@Async
方法需独立于事务:确保异步方法本身不参与当前事务。- 避免事务传播:异步方法的事务传播级别应为
REQUIRES_NEW
(若需独立事务)。
3. 原理解析
(1)事务同步机制
TransactionSynchronization
:Spring 提供的事务同步接口,允许在事务的关键阶段(如提交、回滚)插入自定义逻辑。afterCommit
:事务成功提交后触发,此时数据已持久化到数据库。
(2)执行流程
- 主方法执行:数据库操作进入事务,但未提交。
- 注册回调:通过
registerSynchronization
注册afterCommit
钩子。 - 事务提交:主方法退出,事务提交。
- 触发异步方法:
afterCommit
中调用异步方法,确保数据可见性。
4. 优化方案:@TransactionalEventListener
Spring 4.2+ 提供了更简洁的事务事件监听机制,替代手动注册 TransactionSynchronization
。
(1)定义事件
public class FileTransferEvent {
private String fileId;
private WebUserDto webUserDto;
// 构造方法、Getter/Setter
}
(2)发布事件
@Transactional
public void mainMethod() {
FileInfo fileInfo = fileRepository.save(new FileInfo());
// 发布事件(事务提交后触发)
applicationEventPublisher.publishEvent(
new FileTransferEvent(fileInfo.getFileId(), webUserDto)
);
}
(3)监听事件
@Async
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleFileTransferEvent(FileTransferEvent event) {
fileInfoService.transferFile(event.getFileId(), event.getWebUserDto());
}
(4)优势
- 解耦:将事务提交后的逻辑与主方法分离。
- 简化代码:无需手动管理事务同步器。
- 灵活触发:支持按事务阶段(提交、回滚等)监听事件。
5. 注意事项
(1)事务传播与隔离
- 异步方法的事务:若异步方法需要操作数据库,应使用
@Transactional(propagation = Propagation.REQUIRES_NEW)
启动新事务。 - 避免长事务:异步方法不应阻塞,以免影响主线程性能。
(2)异常处理
- 事务回滚:若主方法事务回滚,
afterCommit
和@TransactionalEventListener
不会触发。 - 异步方法异常:通过
AsyncUncaughtExceptionHandler
捕获异步方法中的异常。
(3)线程上下文
ThreadLocal 数据:事务上下文(如 SecurityContext
)默认不传递到异步线程,需手动传递:
SecurityContext context = SecurityContextHolder.getContext();
CompletableFuture.runAsync(() -> {
SecurityContextHolder.setContext(context);
// 执行业务逻辑
});
6. 总结
- 核心方案:通过事务同步器(
TransactionSynchronization
)或@TransactionalEventListener
确保异步方法在事务提交后执行。 - 最佳实践:
-
- 使用
@TransactionalEventListener(phase = AFTER_COMMIT)
简化代码。 - 异步方法避免依赖未提交事务的数据。
- 合理配置事务传播和线程上下文。
- 使用