Spring Data Jpa中事务上下文无法传递到异步线程的问题解决咨询
Spring Data Jpa中事务上下文无法传递到异步线程的问题解决咨询
我太懂你这个头疼的问题了——用@Transactional标注的主方法里刚持久化完父实体,就启动@Async异步方法去存关联的子实体,结果一直报外键约束错误,核心原因就是Spring的事务上下文是绑定在当前线程的,异步线程拿不到原线程的事务,而且主方法的事务要等整个方法执行完才会提交,异步任务跑的时候,父实体根本还没真正写到数据库里!
先给你拆解下问题根源:你的mainMethod加了@Transactional,整个方法的事务是在方法执行完毕后才会提交的——哪怕你调用了allProcesses.join()等待所有异步任务完成,事务提交的时机还是在mainMethod结束之后。这就导致异步方法里保存子实体时,数据库中根本没有父实体的记录,外键约束自然触发报错。
下面给你几个可行的解决方案,结合你的场景调整:
方案一:拆分逻辑,让父实体事务提前提交
把父实体的持久化和异步任务的启动拆成两个独立的方法,确保父实体的事务在异步任务执行前就提交完成:
// 这个方法单独负责父实体的持久化,事务提交后再触发异步任务 public void handleParentAndAsync(Persisted entity, Param params) { // 先持久化父实体,这个方法的事务执行完就会提交到数据库 persistedRepository.save(entity); // 调用无事务的方法启动异步任务 startAsyncTasks(entity, params); } // 去掉@Transactional,只负责启动异步任务并等待完成 private void startAsyncTasks(Persisted entity, Param params) { List<CompletableFuture<Void>> processList = new ArrayList<>(); for(Item item : params.getData().getItems()){ CompletableFuture<Void> process = produtosPedidoProcessor.asyncPersistance(entity, item); processList.add(process); } CompletableFuture.allOf(processList.toArray(new CompletableFuture[0])).join(); }
这样父实体在进入异步任务前就已经稳稳存在数据库里了,异步方法保存子实体时就能找到对应的外键。
方案二:给异步方法添加独立事务(配合方案一使用)
虽然异步线程拿不到主方法的事务,但可以给asyncPersistance方法加上@Transactional,确保子实体的持久化在自己的事务中执行,避免因为API调用耗时等问题导致的异常:
@Async("someThreadPool") @Transactional public CompletableFuture<Void> asyncPersistance(Persisted entity, Item item){ // 这里放心调用外部API // massive api call // 保存子实体,此时父实体已经在数据库中,外键约束不会报错 someRepository.save(childEntity); return CompletableFuture.completedFuture(null); }
为什么你之前的尝试没生效?
你之前把保存子实体放在thenApply里,本质上还是在异步线程的上下文里执行,而主方法的事务依然没有提前提交——父实体还是没写到数据库,所以外键错误依然会出现,这个思路没触碰到问题的核心。
额外注意点
- 确保
@Async方法的调用是跨Bean的:如果produtosPedidoProcessor是当前类的内部方法,Spring的AOP代理不会生效,@Async注解会失效,这点你的代码看起来是符合要求的,但还是要留意; - 如果业务上要求父实体和子实体必须原子性成功/失败,那异步方案可能不适合你,需要考虑同步处理或者用分布式事务(不过分布式事务复杂度高,尽量避免)。
备注:内容来源于stack exchange,提问作者Lucas Amaral
相关产品推荐
相关产品推荐

