Hibernate异步执行DB任务时如何解决LazyInitializationException?
解决多线程下Hibernate懒加载无会话问题
问题根源
你的核心问题是Spring事务与线程绑定的特性导致的:
- Spring的
@Transactional依赖ThreadLocal存储事务上下文,自定义线程池中的线程不会自动继承原请求线程的事务上下文。 - 你直接通过
this.convertToScoredListing()调用带@Transactional的方法,属于类内部调用,不会触发Spring的AOP代理,注解完全不生效。 - 因此
listingRepo.findById()执行时,Spring会开启临时事务并立即提交,后续访问懒加载字段时会话已关闭,抛出LazyInitializationException。
解决方案1:用Spring @Async替代自定义ExecutorService(推荐)
Spring的@Async会自动管理线程的事务上下文,无需手动处理,完美匹配你的需求:
步骤1:配置异步线程池
@Configuration @EnableAsync public class AsyncConfig implements AsyncConfigurer { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(4); executor.setMaxPoolSize(8); executor.setQueueCapacity(100); executor.setThreadNamePrefix("scoring-"); executor.initialize(); return executor; } }
步骤2:改造Service方法
注意:必须通过代理调用@Async方法,不能用this,可通过AopContext获取代理类:
private ListingScoringFunction scoringFunc; @Transactional(readOnly = true) public Page<Listing> search(ListingSearchParameters params) { Page<Long> pageOfListingIds = listingSearchRepo.searchForListingIds(params); List<CompletableFuture<Listing>> futures = pageOfListingIds.stream() .map(listingId -> ((ListingService) AopContext.currentProxy()).convertToScoredListing(listingId)) .toList(); // 等待所有任务完成并收集结果 List<Listing> scoredListings = futures.stream() .map(CompletableFuture::join) .filter(Objects::nonNull) .toList(); return new PageImpl<>(scoredListings, pageOfListingIds.getPageable(), pageOfListingIds.getTotalElements()); } @Async @Transactional(readOnly = true) public CompletableFuture<Listing> convertToScoredListing(Long eachListingId) { var eachListing = listingRepo.findById(eachListingId).get(); var score = scoringFunc.score(eachListing); Listing scoredListing = ModelMappings.toListing(eachListing, score); return CompletableFuture.completedFuture(scoredListing); }
注意:需要在启动类添加
@EnableAspectJAutoProxy(exposeProxy = true),让AopContext能获取到代理类。
解决方案2:自定义线程池传递事务上下文(保留自定义Executor时使用)
如果不想用@Async,可以手动把原线程的事务上下文传递到线程池任务中:
private ListingScoringFunction scoringFunc; private ExecutorService scoringExecutor; private PlatformTransactionManager transactionManager; @Transactional(readOnly = true) public Page<Listing> search(ListingSearchParameters params) { Page<Long> pageOfListingIds = listingSearchRepo.searchForListingIds(params); List<Listing> scoredListings = scoreListingsById(pageOfListingIds); return new PageImpl<>(scoredListings, pageOfListingIds.getPageable(), pageOfListingIds.getTotalElements()); } private List<Listing> scoreListingsById(Page<Long> listingIds) { // 获取当前线程的事务同步状态 boolean isSyncActive = TransactionSynchronizationManager.isSynchronizationActive(); Map<Object, Object> resources = TransactionSynchronizationManager.getResourceMap(); String txName = TransactionSynchronizationManager.getCurrentTransactionName(); boolean txReadOnly = TransactionSynchronizationManager.isCurrentTransactionReadOnly(); Integer txIsolation = TransactionSynchronizationManager.getCurrentTransactionIsolationLevel(); boolean txActive = TransactionSynchronizationManager.isActualTransactionActive(); List<CompletableFuture<Listing>> resultList = listingIds.stream() .map(eachListingId -> { Supplier<Listing> scoreTask = () -> { try { // 绑定事务上下文到当前线程 if (isSyncActive) { TransactionSynchronizationManager.initSynchronization(); resources.forEach(TransactionSynchronizationManager::bindResource); TransactionSynchronizationManager.setCurrentTransactionName(txName); TransactionSynchronizationManager.setCurrentTransactionReadOnly(txReadOnly); if (txIsolation != null) { TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(txIsolation); } TransactionSynchronizationManager.setActualTransactionActive(txActive); } // 用TransactionTemplate确保事务生效 TransactionTemplate txTemplate = new TransactionTemplate(transactionManager); txTemplate.setReadOnly(true); return txTemplate.execute(status -> { var eachListing = listingRepo.findById(eachListingId).get(); var score = scoringFunc.score(eachListing); return ModelMappings.toListing(eachListing, score); }); } finally { // 解绑事务上下文 if (TransactionSynchronizationManager.isSynchronizationActive()) { TransactionSynchronizationManager.clearSynchronization(); resources.keySet().forEach(TransactionSynchronizationManager::unbindResource); TransactionSynchronizationManager.setCurrentTransactionName(null); TransactionSynchronizationManager.setCurrentTransactionReadOnly(false); TransactionSynchronizationManager.setCurrentTransactionIsolationLevel(null); TransactionSynchronizationManager.setActualTransactionActive(false); } } }; return CompletableFuture.supplyAsync(scoreTask, scoringExecutor); }) .toList(); CompletableFuture.allOf(resultList.toArray(new CompletableFuture[0])).join(); return resultList.stream() .map(eachFuture -> eachFuture.getNow(null)) .filter(Objects::nonNull) .toList(); }
关键注意点
- 避免内部调用
@Transactional方法:必须通过Spring代理类调用,否则注解不生效。 - 多线程下事务独立:每个异步任务会开启自己的只读事务,与原请求事务隔离,符合你的需求。
- 优化查询优先:如果可能,尽量通过
fetch join预加载必要关联字段,减少懒加载触发的DB请求,多线程是优化手段而非替代方案。
内容的提问来源于stack exchange,提问作者RustyTheBoyRobot
相关产品推荐
相关产品推荐

