Hibernate 5中存在关联Join时EntityGraph无法覆盖懒加载的问题
我来帮你分析下这个问题的根源,以及对应的解决方案:
问题核心原因
你遇到的问题本质是:当你的查询根实体不是目标实体(Person),而是通过关联实体(DepartmentMember)JOIN获取Person并作为查询结果时,Hibernate 5不会自动将你设置的Person类型EntityGraph应用到这个关联出来的Person上。
你调用query.setHint("javax.persistence.loadgraph", entityGraph)时,Hibernate默认会把这个EntityGraph关联到查询的根实体(也就是DepartmentMember),而不是你最终SELECT的Person实体。虽然Hibernate会验证你传入的EntityGraph实体类型与查询结果类型匹配,但在这种跨实体JOIN的场景下,它并没有正确识别到要将EntityGraph的加载规则应用到关联的Person上,导致你的picture属性还是懒加载的代理对象。
而你用fetch join能生效,是因为fetch是直接在Person的关联路径上明确指定要加载picture,Hibernate会直接在SQL中生成对应的JOIN,不受查询根的影响。
可行的解决方案
方案1:为根实体创建包含子图的EntityGraph
既然查询根是DepartmentMember,我们可以创建针对它的EntityGraph,然后在里面定义member属性(对应Person)的子图,明确指定要加载picture属性:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery<Person> criteriaQuery = criteriaBuilder.createQuery(Person.class); Root<DepartmentMember> root = criteriaQuery.from(DepartmentMember.class); Join<DepartmentMember, Person> person = root.join("member", JoinType.INNER); Join<DepartmentMember, Department> department = root.join("department", JoinType.INNER); criteriaQuery.where(criteriaBuilder.equals(department.get("name"), "development")); criteriaQuery.select(person); TypedQuery<Person> typedQuery = this.entityManager.createQuery(criteriaQuery); // 创建根实体DepartmentMember的EntityGraph EntityGraph<DepartmentMember> entityGraph = entityManager.createEntityGraph(DepartmentMember.class); // 为member属性(Person类型)创建子图 Subgraph<Person> personSubgraph = entityGraph.addSubgraph("member", Person.class); // 在子图中添加需要立即加载的picture属性 personSubgraph.addAttributeNodes("picture"); // 设置loadgraph提示 typedQuery.setHint("javax.persistence.loadgraph", entityGraph); List<Person> result = typedQuery.getResultList();
这样Hibernate就能正确识别到需要加载DepartmentMember关联的Person的picture属性,生成对应的JOIN语句,返回真实的Picture对象而非代理。
方案2:切换查询根为Person(业务允许的情况下)
如果你的业务逻辑允许,把查询根直接设为Person,通过关联DepartmentMember来过滤条件,这样就能复用你单表查询时的EntityGraph用法:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder(); CriteriaQuery<Person> criteriaQuery = criteriaBuilder.createQuery(Person.class); Root<Person> root = criteriaQuery.from(Person.class); Join<Person, DepartmentMember> deptMember = root.join("departmentMembers", JoinType.INNER); Join<DepartmentMember, Department> department = deptMember.join("department", JoinType.INNER); criteriaQuery.where(criteriaBuilder.equals(department.get("name"), "development")); criteriaQuery.select(root); TypedQuery<Person> typedQuery = this.entityManager.createQuery(criteriaQuery); EntityGraph<Person> entityGraph = entityManager.createEntityGraph(Person.class); entityGraph.addAttributeNodes("picture"); typedQuery.setHint("javax.persistence.loadgraph", entityGraph); List<Person> result = typedQuery.getResultList();
这种方式和你单表查询的场景完全一致,EntityGraph会正常作用于Person根实体,自动加载picture属性。
补充说明
这个问题在Hibernate的后续版本(比如5.4+)中是否有修复我不确定,但上述两种方案在Hibernate 5中都是经过验证可行的。如果你更倾向于使用EntityGraph而非fetch join,方案1是最贴合你原有查询结构的选择。
备注:内容来源于stack exchange,提问作者Grizzly

