Hibernate 4升级至5后出现LazyInitializationException问题求助
我来帮你拆解这个问题——你遇到的核心矛盾其实是自定义Hibernate配置和Spring Boot自动配置的兼容性冲突,尤其是你手动配置的HibernateTransactionManager和Spring Boot默认的JPA事务管理、OpenEntityManagerInView(OEMIV)机制不匹配导致的。
为什么旧版本能正常工作?
在Spring 4+Hibernate 4的组合里,HibernateTransactionManager的Session绑定逻辑相对宽松:事务结束后,Session可能没有立即被销毁或解绑,当控制器序列化对象时,懒加载集合还能拿到有效的Session。而且当时你没开OEMIV也能跑,本质是旧版本的Session生命周期管理刚好覆盖了序列化的时机。
但Hibernate 5对Session的管理做了严格优化,事务结束后会立即释放Session,加上Spring Boot 2的自动配置逻辑和旧版差异很大,之前的“巧合”就不复存在了。
问题根源分析
你手动配置了LocalSessionFactoryBean和HibernateTransactionManager,但:
spring.jpa.open-in-view=true这个配置是**针对Spring Boot默认的JPAEntityManager**生效的,和你自定义的HibernateSessionFactory完全不搭边,所以开了也没用。- 调试时发现
HibernateTransactionManager.doGetTransaction拿不到SessionHolder,每次新建Session——这说明你的自定义事务管理器没有正确和Spring的TransactionSynchronizationManager绑定Session,导致事务结束后Session直接被销毁,序列化时懒加载集合找不到可用的Session。
解决方案(按推荐优先级排序)
方案一:放弃自定义Hibernate配置,改用Spring Boot JPA自动配置(最省心)
Spring Boot的JPA自动配置已经封装了Hibernate的常用逻辑,和OEMIV、事务管理完全无缝集成,不需要手动配置SessionFactory和TransactionManager。
步骤:
- 删除你自定义的
LocalSessionFactoryBean和HibernateTransactionManager配置类。 - 在
application.properties/application.yml里配置Hibernate属性,比如:# 基础JPA配置 spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect # 开启针对JPA的OEMIV,确保序列化时EntityManager保持打开 spring.jpa.open-in-view=true - 保持实体类的
@OneToMany用默认的FetchType.LAZY即可,不需要改成EAGER。
方案二:保留自定义Hibernate配置,手动配置OpenSessionInViewFilter
如果你必须保留自定义的SessionFactory,就要用Hibernate专属的OpenSessionInViewFilter(而不是JPA的OEMIV)来延长Session生命周期。
步骤:
- 在配置类中注册
OpenSessionInViewFilter,指定你的自定义SessionFactory:@Bean public OpenSessionInViewFilter openSessionInViewFilter() { OpenSessionInViewFilter filter = new OpenSessionInViewFilter(); filter.setSessionFactory(sessionFactory()); // 这里传入你自定义的LocalSessionFactoryBean实例 return filter; } - 确保你的
HibernateTransactionManager开启事务同步:@Bean public HibernateTransactionManager transactionManager() { HibernateTransactionManager txManager = new HibernateTransactionManager(); txManager.setSessionFactory(sessionFactory()); // 显式开启事务同步,确保Session绑定到请求上下文 txManager.setTransactionSynchronization(HibernateTransactionManager.SYNCHRONIZATION_ON); return txManager; } - 关闭Spring Boot默认的JPA OEMIV,避免冲突:
spring.jpa.open-in-view=false
方案三:不用OEMIV,手动初始化懒加载集合(性能友好)
如果你不想用OEMIV(它会延长Session生命周期,可能带来性能隐患),可以在服务层主动初始化懒加载集合:
- 方法1:用
Hibernate.initialize()强制加载集合:@Transactional public Template getTemplateById(Long id) { Template template = templateRepository.findById(id).orElse(null); if (template != null) { Hibernate.initialize(template.getLinkToCategories()); // 手动初始化懒加载集合 } return template; } - 方法2:在JPQL查询中用
JOIN FETCH一次性加载关联集合:@Query("SELECT t FROM Template t JOIN FETCH t.linkToCategories WHERE t.id = :id") Template findByIdWithCategories(@Param("id") Long id);
注意事项
- 尽量避免用
FetchType.EAGER,它会导致N+1查询问题,大幅降低查询性能,尤其是集合数据量大的时候。 - 如果用OEMIV,要注意它的副作用:Session会从请求开始一直保持到序列化结束,可能会持有数据库连接更久,高并发场景下要谨慎使用。
内容的提问来源于stack exchange,提问作者Cristian

