访存优化_Hibernate事实:多级访存
訪存優化
在多個級別上檢索根實體及其子關聯是很常見的。
在我們的示例中,我們需要使用其樹,分支和葉子加載森林,并且我們將嘗試查看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>>() {@Overridepublic 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_.idINNER JOIN bagtree bagtree2_ON bagbranch1_.tree_id = bagtree2_.idINNER 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
訪存優化
總結
以上是生活随笔為你收集整理的访存优化_Hibernate事实:多级访存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 虫子旁加个圣怎么读 虫子旁加个圣是什么字
- 下一篇: 雷洛原型 雷洛原型是谁呢