休眠事实:多级访存

在多个级别上检索根实体及其子关联是很常见的。

在我们的示例中,我们需要加载一个包含其树,分支和叶子的森林,并且我们将尝试查看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上获得 。

参考: Hibernate Fact:从我们的JCG合作伙伴 Vlad Mihalcea的Vlad Mihalcea博客博客中进行多级获取 。

翻译自: https://www.javacodegeeks.com/2013/11/hibernate-facts-multi-level-fetching.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/366817.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux系统fuser命令,Linux系统使用Fuser命令的方法

fuser命令是一个非常聪明的unix实用程序&#xff0c;用于查找正在使用某个文件、目录或socket的进程。 它还提供有关拥有该进程的用户和访问类型的信息。。fuser工具显示了使用指定文件或文件系统的每个进程的进程ID(PID)。安装如果你的精简版运行fuser提示如下信息&#xff1a…

网络基础之 Nmap 命令

nmap......转载于:https://www.cnblogs.com/changha0/p/9898020.html

react-router 源码浅析

用 react-router 也用了比较久了&#xff0c;对他的内部工作方式却只是了解皮毛&#xff0c;而且大部分还是通过别人的博客。最近两周打算自己探究一下他的实现。 注意&#xff01;因为我只使用过 v3 版本的 react-router&#xff0c;因为对他的使用方式比较熟悉&#xff0c;所…

前5个有用的隐藏Eclipse功能

Eclipse是野兽。 仅凭其力量才能超越其神秘感的设备。 有人将其称为连续体跨功能器 。 其他人则称它为透湿器 。 是的&#xff0c;它是如此之大&#xff0c;需要花费数年才能掌握。 然后&#xff0c;您的经理出现并告诉您&#xff1a;我们正在使用NetBeans。 开玩笑。 除了Ada…

linux如何解除密码,如何在Linux下解除PDF文件的密码?

【51CTO.com快译】今天&#xff0c;我碰巧与一位朋友共享一个受密码保护的PDF文件。我知道该PDF文件的密码&#xff0c;但不想透露。相反&#xff0c;我只想解除密码&#xff0c;将文件发送给朋友。于是我开始在网上找一些简单的方法&#xff0c;好解除PDF文件的密码保护。上网…

C#中结构体定义并转换字节数组

ref: https://www.cnblogs.com/dafanjoy/p/7818126.html C#中结构体定义并转换字节数组 最近的项目在做socket通信报文解析的时候&#xff0c;用到了结构体与字节数组的转换&#xff1b;由于客户端采用C开发&#xff0c;服务端采用C#开发&#xff0c;所以双方必须保证各自定义结…

解析robots.txt

案例&#xff1a; http://www.taobao.com/robots.txt 学习&#xff1a; User-agent: * 这里的*代表的所有的搜索引擎种类&#xff0c;*是一个通配符Disallow: /admin/ 这里定义是禁止爬寻admin目录下面的目录Disallow: /require/ 这里定义是禁止爬寻require目录下面的目录Disal…

2018移动端页面适配-自适应最新方案直接写px--------通过gulp工作流搭建一体化的移动端开发环境

1.开始 在flexible的GitHub上面写着 由于viewport单位得到众多浏览器的兼容&#xff0c;lib-flexible这个过渡方案已经可以放弃使用&#xff0c;不管是现在的版本还是以前的版本&#xff0c;都存有一定的问题。建议大家开始使用viewport来替代此方案。vw的兼容方案可以参阅《如…

jclouds的命令行界面

序幕 我使用和为jclouds贡献了一年多的时间。 到目前为止&#xff0c;我已经在很多领域广泛使用了它&#xff0c;尤其是在Fuse生态系统中 。 它的强大之处在于它缺少一件事&#xff0c;该工具可用于管理jclouds也提供访问权限的任何云提供商。 类似于EC2命令之类的工具&#xf…

中兴linux下载软件,国产操作系统中兴新支点使用WPS For Linux办公软件的体验报告...

以下将给你带来在国产操作系统中兴新支点操作系统下使用WPS For Linux办公软件的体验报告&#xff0c;WPS For Linux提供Deb、Rpm、Tar.xz、Snap软件包&#xff0c;你可以选择Tar.xz源码包编译安装&#xff0c;或在系统自带的软件中心下安装&#xff0c;也可以参考采用snap方式…

Java 教程(开发环境配置+基础语法)

Java 开发环境配置 在本章节中我们将为大家介绍如何搭建Java开发环境。 window系统安装java 下载JDK 首先我们需要下载java开发工具包JDK&#xff0c;下载地址&#xff1a;http://www.oracle.com/technetwork/java/javase/downloads/index.html&#xff0c;点击如下下载按钮&am…

数据采集工具Telegraf:简介及安装

接着上一篇博客&#xff1a;InfluxDB简介及安装&#xff0c;这篇博客介绍下Linux环境下Telegraf安装以及其功能特点。。。 官网地址&#xff1a;influxdata 官方文档&#xff1a;telegraf文档 环境&#xff1a;CentOS7.4 64位 Telegraf版本&#xff1a;0.11.1-1 一、Telegraf介…

初探小程序插件

插播公司招聘信息&#xff1a; https://cnodejs.org/topic/5a915706653c43b914684f90 小程序插件可以干嘛&#xff1f; 周二晚上&#xff08;3.13&#xff09;的一个小程序新功能发布了-【小程序插件】&#xff0c;一开始以为是小程序发布了类似npm的组件管理工具&#xff0c;…

流畅和稳定的API的Lambda

几周前&#xff0c;我写了关于Java 8 lambda的介绍 。 在本简介中&#xff0c;我解释了什么是lambda以及如何将它们与Java 8中也引入的新Stream API结合使用。 Stream API为集合提供了更实用的接口。 此接口在很大程度上取决于lambda。 但是&#xff0c;lambda不仅具有改进的收…

linux 内存使用原理,linux中内存使用原理

首先介绍一下linux中内存是如何使用的。当有应用需要读写磁盘数据时&#xff0c;由系统把相关数据从磁盘读取到内存&#xff0c;如果物理内存不够&#xff0c;则把内存中的部分数据导入到磁盘&#xff0c;从而把磁盘的部分空间当作虚拟内存来使用&#xff0c;也称为Swap。如果给…

Confluence 6 站点备份和恢复

Atlassian 推荐针对生产环境中安装使用的 Confluence 使用原始数据库工具备份策略。 在默认的情况下&#xff0c;Confluence 每天都会备份所有数据和附件到 XML 文件备份中。这些文件被称为 XML 站点备份&#xff0c;同时这些文件存储在 Confluence home 目录中的 backups 目录…

休眠事实:等于和HashCode

每个Java对象都继承了equals和hashCode方法&#xff0c;但它们仅对Value对象有用&#xff0c;对面向无状态行为的对象毫无用处。 尽管使用“ ”运算符比较引用很简单&#xff0c;但是对于对象相等而言&#xff0c;事情要复杂一些。 由于您负责告诉平等性对特定对象类型的含义…

从mysql向HBase+Phoenix迁移数据的心得总结

* 转载请注明出处 - yosql473 - 格物致知&#xff0c;经世致用 mysql -> HBase Phoenix 1.总体方案有哪些&#xff1f; 1&#xff09;通过Sqoop直接从服务器(JDBC方式)抽取数据到HBase中 因为数据量非常大&#xff0c;因此优先考虑用Sqoop和MR抽取。 使用Sqoop抽取数据有一…

玩转异步 JS :async/await 简明教程(附视频下载)

课程介绍 在软件开发领域&#xff0c;简洁的代码 > 容易阅读的代码 > 容易维护的代码&#xff0c;而 ES2017 中的 async/await 特性能让我们编写出相比回调地狱和 Promise 链式调用更直观、更容易理解的代码&#xff0c;await 关键字接收一个 Promise&#xff0c;等待代码…

linux 无法找到函数定义,找到定义Linux函数的位置

使用手册页对于基本的C函数&#xff0c;该手册页应该工作。man 2 readman 3 printf第2节为系统调用(直接到内核)&#xff0c;而第3是用于标准C库调用。您通常可以省略该部分&#xff0c;并且人将自己弄清楚您需要什么。请注意&#xff0c;您可能需要采取额外步骤在系统上获取与…