Hibernate懒加载场景下未关Session致应用卡死,关闭后无法访问外键如何解决
问题根因
你当前的实现本质是手动实现了错误的Open Session in View反模式,两个核心认知错误直接导致了现在的矛盾:
- 手动调用
sessionFactory.openSession()会从数据库连接池申请新连接,你既不关闭Session也不归还连接,大量请求堆积后连接池被完全占满,应用自然卡死 flush()仅负责将Session一级缓存中暂存的增删改操作同步执行到数据库,既不会关闭Session、释放数据库连接,也不会改变懒加载的行为逻辑,所以完全解决不了问题- Hibernate懒加载生效的唯一前提是:调用关联字段getter方法时,该实体绑定的Session处于打开状态。你之前靠不关闭Session来保证懒加载可用,本质是用资源泄漏换功能运行,完全不具备生产可用性。
最优落地方案(适配Spring+Hibernate技术栈)
1. 彻底废弃手动管理Session的写法,交给Spring托管生命周期
你用Spring整合Hibernate的核心优势就是容器帮你管理事务和Session资源,完全不需要手动open/flush/close Session:
- 先配置好Hibernate事务管理器,在Service层(注意不要加在DAO层)的业务方法上添加
@Transactional注解 - Spring会在业务方法启动时自动打开Session、绑定到当前线程,方法执行结束(正常返回/抛出异常)时自动完成事务提交/回滚、关闭Session、将数据库连接归还连接池,从根源上消除连接泄漏
- DAO层直接通过
sessionFactory.getCurrentSession()获取当前线程绑定的Session即可,不需要手动做任何资源释放操作。
2. 懒加载问题按需解决,不要靠延长Session生命周期硬凑
根据业务场景选对应方案即可,不需要为了懒加载一直占着Session:
- 固定需要加载某些关联字段的场景:直接在HQL中用
join fetch一次性关联查询出需要的对象,从查询层面就把关联数据加载完成,Session关闭后也能正常读取关联字段。比如你示例中的方法如果需要读取capaWfDetails关联对象,直接把HQL调整为select distinct c from QmsCapaWfUserDetails c left join fetch c.capaWfDetails where c.capaWfDetails.id in (:wfIdsList)即可,还能避免N+1查询问题 - 不同接口需要关联字段不固定的场景:在Service层的事务上下文内(即Session未关闭时),要么手动调用对应关联字段的getter方法触发代理加载,要么用
Hibernate.initialize(实体对象.get关联属性())手动完成懒加载字段初始化,再把处理完的实体返回给上层 - 返回逻辑灵活多变的场景:不要直接把Hibernate托管的持久化实体返回给上层(Controller/前端/其他RPC调用方),直接通过HQL构造DTO投影,只查询业务需要的字段封装成普通POJO返回,从根源上避免懒加载问题。
3. 明确规避错误方案
不要为了省事儿配置全局Open Session in View(OSIV)拦截器把Session生命周期拉长到整个HTTP请求结束,这种方案和你现在手动不关Session的问题本质一致——长请求、慢接口都会长期占用数据库连接,高并发场景下同样会打满连接池导致应用不可用。
现有代码改造示例
DAO层改造后不需要手动开关Session,高频关联字段直接通过join fetch加载:
public List<QmsCapaWfUserDetails> getCapaWfUserDetailsesByWfIdsList(List<Integer> wfIdsList) { logger.debug("QmsCapaDao getCapaWfUserDetailsesByWfIdsList() Start"); if (Utilities.IsEmpty(wfIdsList)) { return Collections.emptyList(); } Session hibernateSession = sessionFactory.getCurrentSession(); String hql = "select distinct c from QmsCapaWfUserDetails c left join fetch c.capaWfDetails where c.capaWfDetails.id in (:wfIdsList)"; Query query = hibernateSession.createQuery(hql); query.setParameterList("wfIdsList", wfIdsList); List<QmsCapaWfUserDetails> result = query.list(); logger.debug("QmsCapaDao getCapaWfUserDetailsesByWfIdsList() End"); return result; }
只需要在调用该DAO方法的Service方法上添加@Transactional注解,即可保证整个业务执行过程中Session可用,方法结束后自动释放资源。
补充:如果现有代码存量大、很多地方都在直接读取懒加载字段,优先用join fetch覆盖高频访问的关联字段,再逐步将对外暴露的持久化实体替换为DTO,不要走延长Session生命周期的捷径。
内容的提问来源于stack exchange,提问作者Shaik Bajivali
相关产品推荐
相关产品推荐

