Spring中如何让非数据库/消息队列相关的普通类具备事务感知能力?
Spring 普通类接入事务感知的实现方案
Spring 提供了成熟的事务同步机制,完全可以让和数据库、消息队列无关的普通类具备事务感知能力,不需要依赖额外组件。
核心实现思路
Spring 的事务上下文会和当前线程绑定,我们可以给运行中的事务注册自定义回调钩子,在事务提交、回滚、完成等节点触发对应逻辑。针对文件写入的场景,只需要把直接写盘的动作调整为「先缓存待写入内容,事务提交成功后再刷入磁盘,事务回滚则直接丢弃缓存」即可实现回滚能力。
具体实现步骤
第一步:开启Spring事务支持
确保项目已经引入spring-tx依赖,并且在启动类上添加@EnableTransactionManagement注解开启事务注解能力,哪怕项目没有配置数据库数据源,该能力也可以正常启用。
第二步:改造WriterToFileSystem类
import org.springframework.transaction.support.TransactionSynchronizationAdapter; import org.springframework.transaction.support.TransactionSynchronizationManager; import java.io.ByteArrayOutputStream; import java.io.FileOutputStream; import java.io.IOException; public class WriterToFileSystem { private final String fileName = "current_file_name"; // 线程绑定的待写入缓存,对应当前线程的事务上下文 private final ThreadLocal<ByteArrayOutputStream> pendingBuffer = ThreadLocal.withInitial(ByteArrayOutputStream::new); public void addToFile(String textToAppendToFile) throws IOException { // 判断当前是否处于事务上下文中 if (TransactionSynchronizationManager.isActualTransactionActive()) { // 事务存在时先写入缓存 pendingBuffer.get().write(textToAppendToFile.getBytes()); // 给当前事务注册回调,同一事务仅注册一次 if (TransactionSynchronizationManager.getResource(this) == null) { TransactionSynchronizationManager.bindResource(this, Boolean.TRUE); TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { // 事务提交成功,把缓存内容写入原文件 try (FileOutputStream fos = new FileOutputStream(fileName, true)) { pendingBuffer.get().writeTo(fos); } catch (IOException e) { throw new RuntimeException("写入文件失败", e); } } @Override public void afterCompletion(int status) { // 事务完成(不管提交还是回滚)清空缓存和绑定资源,避免内存泄漏 pendingBuffer.remove(); TransactionSynchronizationManager.unbindResource(WriterToFileSystem.this); } }); } } else { // 无事务的场景直接写盘 try (FileOutputStream fos = new FileOutputStream(fileName, true)) { fos.write(textToAppendToFile.getBytes()); } } } }
第三步:修正业务类注解
原代码中的@Transactionl拼写有误,修正为正确的@Transactional即可:
import org.springframework.transaction.annotation.Transactional; public class Appender { private WriterToFileSystem writer; @Transactional public void append(String someLongText) throws IOException { writer.addToFile(someLongText); // 后续任何业务逻辑抛出异常,都会触发事务回滚,缓存内容直接丢弃不会写入文件 } }
拓展说明
- 如果你的场景需要提前写入文件、回滚时撤销改动,可以把逻辑调整为:写入时先写临时文件,事务提交时把临时文件内容合并到原文件/替换原文件,回滚时直接删除临时文件即可。
- 如果需要支持嵌套事务,可以通过
TransactionSynchronizationManager.getCurrentTransactionIsolationLevel()、TransactionSynchronizationManager.isCurrentTransactionReadOnly()等方法获取事务属性,做对应层级的缓存隔离。
内容的提问来源于stack exchange,提问作者M-Soley
相关产品推荐
相关产品推荐

