Spring MVC多线程场景下保存Hibernate实体出现LazyInitializationException问题
问题根因
- Spring 默认将Hibernate Session(JPA EntityManager)的生命周期与请求线程绑定,主线程的请求处理完成后Session就会被关闭
jpaRepository.getOne()返回的是懒加载代理对象,不会立即执行数据库查询,只有访问属性时才触发查询,子线程没有存活的Session,因此抛出LazyInitializationException- 你自行创建的子线程不受Spring事务管理器管理,就算
createEntity方法加了@Transactional注解也不会生效,无法自动为子线程创建新的Session
可行解决方案
方案1:替换getOne为findById(改动最小,优先推荐)
findById会立即执行SQL查询返回真实实体,不会生成懒加载代理,完全避开Session依赖问题,只需修改createEntity方法的查询逻辑:
// 替换原有的entitySaved = jpaRepository.getOne((Long) entity.getId()); entitySaved = jpaRepository.findById((Long) entity.getId()) .orElseThrow(() -> new EntityNotFoundException("实体不存在,ID:" + entity.getId()));
方案2:使用Spring异步管理替代自定义子线程
不要手动创建子线程,改用Spring自带的异步任务管理,Spring会自动为异步线程绑定独立的Session,事务注解也能正常生效:
- 项目启动类添加
@EnableAsync注解开启异步功能 - 将通知标记已读的逻辑封装到单独的Bean方法中,方法添加
@Async和@Transactional注解 - 控制器中直接调用该异步方法即可,无需自行维护线程池
该方案符合Spring开发规范,还能避免自行管理线程可能带来的资源泄露、并发数失控问题。
方案3:手动为子线程绑定Session
如果必须保留自定义子线程的实现,可以在子线程执行数据库操作前手动绑定EntityManager:
// 先注入EntityManagerFactory @Autowired private EntityManagerFactory entityManagerFactory; // 子线程run方法内处理逻辑 public void run() { EntityManager em = entityManagerFactory.createEntityManager(); try { EntityManagerHolder emHolder = new EntityManagerHolder(em); TransactionSynchronizationManager.bindResource(entityManagerFactory, emHolder); // 此处执行你的数据库操作,比如调用createEntity方法 } finally { EntityManagerHolder emHolder = (EntityManagerHolder) TransactionSynchronizationManager.unbindResource(entityManagerFactory); EntityManagerFactoryUtils.closeEntityManager(emHolder.getEntityManager()); } }
该方案代码侵入性较高,需要手动管理资源,不优先推荐。
额外优化建议
你现在的实现是把ES查询到的全量Notification实体传到子线程,实际仅需要传输通知ID列表即可,还可以进一步优化为批量更新,不用循环单条查询修改:
// 直接写JPQL批量更新,一次操作完成所有通知的已读标记 @Modifying @Query("update Notification n set n.opened = true where n.id in :idList") void batchMarkOpened(List<Long> idList);
该方案省略了查询、merge的步骤,性能更高,也完全避开了懒加载相关的问题。
内容的提问来源于stack exchange,提问作者user7332139
相关产品推荐
相关产品推荐

