【发布时间】:2018-01-25 17:02:45
【问题描述】:
我希望有办法微调 JPA/Hibernate 以管理以下用例。 我有以下对象模型:
class Parent {
...
@OneToMany(mappedBy = "definizione", fetch = FetchType.LAZY)
private List<Child> childs;
...
}
class Child {
...
@ManyToOne(fetch = FetchType.LAZY)
private GrandChild grandChild;
...
}
class GrandChild {
...
}
然后我执行以下代码:
Parent parent = entityManager.find(id, Parent.class); // (1)
List<Child> childs = parent.getChilds(); // (2)
GrandChild grandChild = null;
for(Child child : childs) {
grandChild = child.getGrandChild(); // (3)
//do somthing with childs
}
我想要达到的是:
- 根据用例调整休眠,即不更改实体类;
-
让休眠执行两个查询:
select parent0_.* -- all Parent columns from PARENTS parent0_ where parent0_.ID=? SELECT childs0_.* -- all Child columns grandchild1_.* -- all GrandChild columns FROM childs childs0_ LEFT OUTER JOIN grand_childs grandchild1_ ON childs0_.grand_child_id = grandchild1_.id WHERE childs0_.parent_id =?
上面的sn-p与Child-GrandChildlazy fetch的基本行为是:
- (1) 对
Parent实体执行一次查询; - (2)对
Parent实体的所有Child实体进行一次查询; - (3) n 个查询,每个
GrandChild实体一个。
通过阅读Hibernate Fetching,我找到了以下解决方案,但都没有达到我想要的效果:
更改实体类获取策略
class Child {
...
@ManyToOne(fetch = FetchType.EAGER)
private GrandChild grandChild;
...
}
优点:执行的查询数量是所需的;
缺点:此解决方案会影响其他用例,出于某些原因,我不想这样做在实体类级别更改获取策略。
通过查询动态获取
这种情况对 jpql 和 Crieria 查询都有效。
final Parent parent = entityManager.createQuery(
"SELECT p FROM Parent p LEFT JOIN FETCH p.childs c JOIN FETCH c.grandChild WHERE p.id = :id",
Parent.class
)
.setParameter("id", id)
.getSingleResult();
List<Child> childs = parent.getChilds();
GrandChild grandChild = null;
for (Child child : childs) {
grandChild = child.getGrandChild();
//do somthing with childs
}
执行的查询是:
SELECT parent0_.*, -- all Parent fields
childs1_.*, -- all Child fields
grandchild2_.* -- all GrandChild fields
FROM parents parent0_
LEFT OUTER JOIN childs childs1_ ON parent0_.id = childs1_.parent_id
LEFT JOIN grand_childs grandchild2_ ON childs1_.grand_child_id = grandchild2_.id
WHERE parent0_.id =?
优点:只执行了一个查询。
缺点:从数据库中加载了很多重复数据,我不想再加载父实体然后一次。
通过 JPA 实体图动态获取
@Entity
@NamedEntityGraph(
name = "parent.childs.grandchild",
attributeNodes = {
@NamedAttributeNode(value = "childs", subgraph = "childs.grandchild")
},
subgraphs = {
@NamedSubgraph(
name = "childs.grandchild",
attributeNodes = {
@NamedAttributeNode(value = "grandChild")
}
)
}
)
public class Parent extends BaseEntity{
...
}
以及要加载的代码:
final Parent parent = entityManager.find(
Parent.class,
id,
Collections.singletonMap(
"javax.persistence.fetchgraph",
entityManager.getEntityGraph( "parent.childs.grandchild" )
)
);
List<Child> childs = parent.getChilds();
GrandChild grandChild = null;
for (Child child : childs) {
grandChild = child.getGrandChild();
//do somthing with childs
}
执行的查询与通过查询动态获取相同,因此优缺点相同。
【问题讨论】:
-
@JBNizet,我阅读了文档,但我没有找到一种方法来做我只会在子孙子协会上做的事情,所以我发布了这个问题,询问是否有可能,或者这样做的唯一方法是更改获取策略
-
我已阅读您的更新,关于通过查询动态获取 Hibernate 不会多次加载您的父对象,因此您最终会加载一个父对象。您无法在单个查询中实现您想要的。