访存优化
在多个级别上检索根实体及其子关联是很常见的。
在我们的示例中,我们需要使用其树,分支和叶子加载森林,并且我们将尝试查看Hibernate在三种集合类型上的表现:集合,索引列表和包。
使用集和索引列表很简单,因为我们可以通过运行以下JPA-QL查询来加载所有实体:
Forest f = entityManager.createQuery(
"select f " +
"from Forest f " +
"join fetch f.trees t " +
"join fetch t.branches b " +
"join fetch b.leaves l ", Forest.class)
.getSingleResult();
执行SQL查询是:
SELECT forest0_.id AS id1_7_0_,
trees1_.id AS id1_18_1_,
branches2_.id AS id1_4_2_,
leaves3_.id AS id1_10_3_,
trees1_.forest_fk AS forest_f3_18_1_,
trees1_.index AS index2_18_1_,
trees1_.forest_fk AS forest_f3_7_0__,
trees1_.id AS id1_18_0__,
trees1_.index AS index2_0__,
branches2_.index AS index2_4_2_,
branches2_.tree_fk AS tree_fk3_4_2_,
branches2_.tree_fk AS tree_fk3_18_1__,
branches2_.id AS id1_4_1__,
branches2_.index AS index2_1__,
leaves3_.branch_fk AS branch_f3_10_3_,
leaves3_.index AS index2_10_3_,
leaves3_.branch_fk AS branch_f3_4_2__,
leaves3_.id AS id1_10_2__,
leaves3_.index AS index2_2__
FROM forest forest0_
INNER JOIN tree trees1_ ON forest0_.id = trees1_.forest_fk
INNER JOIN branch branches2_ ON trees1_.id = branches2_.tree_fk
INNER JOIN leaf leaves3_ ON branches2_.id = leaves3_.branch_fk
但是,当我们的子级关联映射为Bags时,相同的JPS-QL查询将引发“ org.hibernate.loader.MultipleBagFetchException”。
万一您不能更改映射(用集合或索引列表替换包),您可能会想尝试以下方法:
BagForest forest = entityManager.find(BagForest.class, forestId);
for (BagTree tree : forest.getTrees()) {
for (BagBranch branch : tree.getBranches()) {
branch.getLeaves().size();
}
}
但这效率低下,无法生成大量SQL查询:
select trees0_.forest_id as forest_i3_1_1_, trees0_.id as id1_3_1_, trees0_.id as id1_3_0_, trees0_.forest_id as forest_i3_3_0_, trees0_.index as index2_3_0_ from BagTree trees0_ where trees0_.forest_id=?
select branches0_.tree_id as tree_id3_3_1_, branches0_.id as id1_0_1_, branches0_.id as id1_0_0_, branches0_.index as index2_0_0_, branches0_.tree_id as tree_id3_0_0_ from BagBranch branches0_ where branches0_.tree_id=?
select leaves0_.branch_id as branch_i3_0_1_, leaves0_.id as id1_2_1_, leaves0_.id as id1_2_0_, leaves0_.branch_id as branch_i3_2_0_, leaves0_.index as index2_2_0_ from BagLeaf leaves0_ where leaves0_.branch_id=?
select leaves0_.branch_id as branch_i3_0_1_, leaves0_.id as id1_2_1_, leaves0_.id as id1_2_0_, leaves0_.branch_id as branch_i3_2_0_, leaves0_.index as index2_2_0_ from BagLeaf leaves0_ where leaves0_.branch_id=?
select branches0_.tree_id as tree_id3_3_1_, branches0_.id as id1_0_1_, branches0_.id as id1_0_0_, branches0_.index as index2_0_0_, branches0_.tree_id as tree_id3_0_0_ from BagBranch branches0_ where branches0_.tree_id=?
select leaves0_.branch_id as branch_i3_0_1_, leaves0_.id as id1_2_1_, leaves0_.id as id1_2_0_, leaves0_.branch_id as branch_i3_2_0_, leaves0_.index as index2_2_0_ from BagLeaf leaves0_ where leaves0_.branch_id=?
select leaves0_.branch_id as branch_i3_0_1_, leaves0_.id as id1_2_1_, leaves0_.id as id1_2_0_, leaves0_.branch_id as branch_i3_2_0_, leaves0_.index as index2_2_0_ from BagLeaf leaves0_ where leaves0_.branch_id=?
因此,我的解决方案是简单地获取最低级别的子级,并在实体层次结构中一直获取所有需要的关联。
运行此代码:
List<BagLeaf> leaves = transactionTemplate.execute(new TransactionCallback<List<BagLeaf>>() {
@Override
public List<BagLeaf> doInTransaction(TransactionStatus transactionStatus) {
List<BagLeaf> leaves = entityManager.createQuery(
"select l " +
"from BagLeaf l " +
"inner join fetch l.branch b " +
"inner join fetch b.tree t " +
"inner join fetch t.forest f " +
"where f.id = :forestId",
BagLeaf.class)
.setParameter("forestId", forestId)
.getResultList();
return leaves;
}
});
仅生成一个SQL查询:
SELECT bagleaf0_.id AS id1_2_0_,
bagbranch1_.id AS id1_0_1_,
bagtree2_.id AS id1_3_2_,
bagforest3_.id AS id1_1_3_,
bagleaf0_.branch_id AS branch_i3_2_0_,
bagleaf0_.index AS index2_2_0_,
bagbranch1_.index AS index2_0_1_,
bagbranch1_.tree_id AS tree_id3_0_1_,
bagtree2_.forest_id AS forest_i3_3_2_,
bagtree2_.index AS index2_3_2_
FROM bagleaf bagleaf0_
INNER JOIN bagbranch bagbranch1_
ON bagleaf0_.branch_id = bagbranch1_.id
INNER JOIN bagtree bagtree2_
ON bagbranch1_.tree_id = bagtree2_.id
INNER JOIN bagforest bagforest3_
ON bagtree2_.forest_id = bagforest3_.id
WHERE bagforest3_.id = ?
我们得到了一个叶子对象的列表,但是每个叶子还获取了分支,后者也获取了树,然后获取了森林。 不幸的是,Hibernate无法从这样的查询结果神奇地创建上下层次结构。
尝试通过以下方式进入袋子:
leaves.get(0).getBranch().getTree().getForest().getTrees();
只是抛出LazyInitializationException,因为我们试图在打开的持久性上下文之外访问未初始化的惰性代理列表。
因此,我们只需要从Leaf对象的List自己重新创建Forest层次结构即可。
这就是我的方法:
EntityGraphBuilder entityGraphBuilder = new EntityGraphBuilder(new EntityVisitor[] {
BagLeaf.ENTITY_VISITOR, BagBranch.ENTITY_VISITOR, BagTree.ENTITY_VISITOR, BagForest.ENTITY_VISITOR
}).build(leaves);
ClassId<BagForest> forestClassId = new ClassId<BagForest>(BagForest.class, forestId);
BagForest forest = entityGraphBuilder.getEntityContext().getObject(forestClassId);
EntityGraphBuilder是我编写的一个实用程序,它接受EntityVisitor对象的数组并将其应用于访问的对象。 递归处理到Forest对象,并且我们用新的Hibernate集合替换了Hibernate集合,并将每个子代添加到父子代集合。
由于替换了子级集合,因此不安全地在新的Persistence Context中重新附加/合并此对象是比较安全的,因为所有Bags都将标记为脏。
这是实体使用其访客的方式:
private <T extends Identifiable, P extends Identifiable> void visit(T object) {
Class<T> clazz = (Class<T>) object.getClass();
EntityVisitor<T, P> entityVisitor = visitorsMap.get(clazz);
if (entityVisitor == null) {
throw new IllegalArgumentException("Class " + clazz + " has no entityVisitor!");
}
entityVisitor.visit(object, entityContext);
P parent = entityVisitor.getParent(object);
if (parent != null) {
visit(parent);
}
}
基本的EntityVisitor看起来像这样:
public void visit(T object, EntityContext entityContext) {
Class<T> clazz = (Class<T>) object.getClass();
ClassId<T> objectClassId = new ClassId<T>(clazz, object.getId());
boolean objectVisited = entityContext.isVisited(objectClassId);
if (!objectVisited) {
entityContext.visit(objectClassId, object);
}
P parent = getParent(object);
if (parent != null) {
Class<P> parentClass = (Class<P>) parent.getClass();
ClassId<P> parentClassId = new ClassId<P>(parentClass, parent.getId());
if (!entityContext.isVisited(parentClassId)) {
setChildren(parent);
}
List<T> children = getChildren(parent);
if (!objectVisited) {
children.add(object);
}
}
}
此代码打包为实用程序,并且通过扩展EntityVisitors来进行自定义,如下所示:
public static EntityVisitor<BagForest, Identifiable> ENTITY_VISITOR = new EntityVisitor<BagForest, Identifiable>(BagForest.class) {};
public static EntityVisitor<BagTree, BagForest> ENTITY_VISITOR = new EntityVisitor<BagTree, BagForest>(BagTree.class) {
public BagForest getParent(BagTree visitingObject) {
return visitingObject.getForest();
}
public List<BagTree> getChildren(BagForest parent) {
return parent.getTrees();
}
public void setChildren(BagForest parent) {
parent.setTrees(new ArrayList<BagTree>());
}
};
public static EntityVisitor<BagBranch, BagTree> ENTITY_VISITOR = new EntityVisitor<BagBranch, BagTree>(BagBranch.class) {
public BagTree getParent(BagBranch visitingObject) {
return visitingObject.getTree();
}
public List<BagBranch> getChildren(BagTree parent) {
return parent.getBranches();
}
public void setChildren(BagTree parent) {
parent.setBranches(new ArrayList<BagBranch>());
}
};
public static EntityVisitor<BagLeaf, BagBranch> ENTITY_VISITOR = new EntityVisitor<BagLeaf, BagBranch>(BagLeaf.class) {
public BagBranch getParent(BagLeaf visitingObject) {
return visitingObject.getBranch();
}
public List<BagLeaf> getChildren(BagBranch parent) {
return parent.getLeaves();
}
public void setChildren(BagBranch parent) {
parent.setLeaves(new ArrayList<BagLeaf>());
}
};
这不是“本身”的访客模式,但与它有点类似。 尽管只使用索引列表或集合总会更好,但是您仍然可以使用单个查询Bags来获得关联图。
- 代码可在GitHub上获得 。
翻译自: https://www.javacodegeeks.com/2013/11/hibernate-facts-multi-level-fetching.html
访存优化