Fork me on GitHub

事务中启用事务问题

开发的时候遇到了一个问题,事务还没提交,开启了异步操作,异步操作里面会读取事务里面修改后的数据问题

问题

如下代码

  @Transactional
    public void processData(Long dataId) {
        saveData(dataId);
        afterCommitExecutor.execute(() -> {
                        fetchDataAfterCommit(dataId);
        });
    }

fetchDataAfterCommit(Long dataId){
  Object data = getData(dataId);
}

如果线程执行的时候,事务还没提交,
此时fetchDataAfterCommit方法里面是获取不到数据的,
此时会抛出空指针异常,解决方法得让事务提交后再开启线程

解决方案

方案一

注册事务同步回调

@Transactional
    public void processData(Long dataId) {
        saveData(dataId);
        
        TransactionSynchronizationManager.registerSynchronization(
            new TransactionSynchronizationAdapter() {  // 使用适配器
                @Override
                public void afterCommit() {
                    afterCommitExecutor.execute(() -> {
                        fetchDataAfterCommit(dataId);
                    });
                }
            }
        );
    }

方案二

使用 @TransactionalEventListener

@Component
public class DataReadyListener {
    
    @Async
    @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
    public void handleDataReadyEvent(DataReadyEvent event) {
        // 这里会在主事务提交后异步执行
        fetchDataAfterCommit(event.getDataId());
    }
    
    private void fetchDataAfterCommit(Long dataId) {
        // 获取数据的逻辑
    }
}

方案三

手动控制事务提交

@Service
public class DataService {
    
    @Autowired
    private PlatformTransactionManager transactionManager;
    
    public void processData() {
        TransactionStatus status = transactionManager.getTransaction(
            new DefaultTransactionDefinition()
        );
        
        try {
            // 1. 业务处理和数据保存
            
            transactionManager.commit(status);
            
            // 2. 事务提交后异步执行
            CompletableFuture.runAsync(() -> {
                fetchDataAfterCommit(dataId);
            });
            
        } catch (Exception e) {
            transactionManager.rollback(status);
            throw e;
        }
    }
}

方案四

使用消息队列解耦

这种方式也有可能存在事务没有执行完,消息队列先执行的情况,尤其是长事务的情况,使用这种方式一定要到最后才能发送消息队列

@Service
public class DataService {
    
    @Transactional
    public void processData() {
        // 1. 业务处理和数据保存
        
        // 2. 发送消息到消息队列
        messageQueue.send(new DataReadyMessage(dataId));
    }
}

@Component
public class DataConsumer {
    
    @RabbitListener(queues = "data.queue")
    public void consumeDataMessage(DataReadyMessage message) {
        // 消息队列消费时,事务一定已经提交了
        fetchDataAfterCommit(message.getDataId());
    }
}
posted @ 2026-01-14 10:56  Banana_Fish  阅读(33)  评论(0)    收藏  举报