【发布时间】:2017-11-30 13:06:46
【问题描述】:
我在合理的时间内从数据库中完全加载非常复杂的对象以及查询的数量合理时遇到了问题。
我的对象有很多嵌入的实体,每个实体都有对另一个实体的引用,另一个实体又引用另一个等等(所以,嵌套级别是 6)
所以,我创建了示例来演示我想要什么: https://github.com/gladorange/hibernate-lazy-loading
我有用户。
用户拥有@OneToMany 收藏的最喜欢的橙子、苹果、葡萄和桃子。每个 Grapevine 都有@OneToMany 的葡萄收藏。每个水果都是只有一个字符串字段的另一个实体。
我正在用每种类型的 30 个最喜欢的水果创建用户,每个葡萄藤有 10 个葡萄。所以,我在 DB 中总共有 421 个实体 - 30*4 个水果、100*30 个葡萄和一个用户。
而我想要的是:我想使用不超过 6 个 SQL 查询来加载它们。 并且每个查询都不应该产生大的结果集(对于该示例来说,大是包含超过 200 条记录的结果集)。
我的理想解决方案如下:
6 个请求。第一个请求返回有关用户的信息,结果集的大小为 1。
第二次请求返回此用户的苹果信息,结果集大小为 30。
第三个、第四个和第五个请求返回相同的结果,与第二个(结果集大小 = 30)相同,但对于葡萄藤、橙子和桃子。
第六个请求返回所有葡萄藤的葡萄
这在 SQL 世界中非常简单,但我无法使用 JPA (Hibernate) 来实现。
我尝试了以下方法:
使用 fetch join,例如
from User u join fetch u.oranges ...。这太可怕了。结果集为 30*30*30*30,执行时间为 10 秒。请求数 = 3。我在没有葡萄的情况下尝试过,用葡萄你会得到 x10 大小的结果集。只需使用延迟加载。这是本示例中的最佳结果(使用 @Fetch= SUBSELECT 用于葡萄)。但在那种情况下,我需要手动迭代每个元素集合。此外,subselect fetch 的设置过于全局,所以我想要一些可以在查询级别上工作的东西。结果集和时间接近理想。 6 个查询和 43 毫秒。
加载实体图。与 fetch join 相同,但它也请求每个葡萄获取它的葡萄藤。然而,结果时间更好(6秒),但仍然很糟糕。请求数 > 30。
-
我试图通过在单独的查询中“手动”加载实体来欺骗 JPA。喜欢:
从 id=1 的用户中选择 u; SELECT a FROM Apple where a.user_id=1;
这比延迟加载要差一些,因为它需要对每个集合进行两次查询:第一次查询手动加载实体(我可以完全控制这个查询,包括加载关联的实体),第二次查询延迟加载Hibernate 本身的相同实体(这是由 Hibernate 自动执行的)
执行时间为 52,查询数 = 10(用户 1 个,葡萄 1 个,每个水果集合 4*2)
实际上,结合 SUBSELECT 提取的“手动”解决方案允许我使用“简单”提取连接在一个查询中加载必要的实体(如 @OneToOne 实体)所以我将使用它。但我不喜欢我必须执行两个查询来加载集合。
有什么建议吗?
【问题讨论】:
-
我会在 5 个查询中手动完成:首先使用热切的
@OneToMany结合第二个查询,然后按照描述的请求 3-6 进行一个,然后在 Java 代码中组装一个对象。当然,不太优雅,但不需要@OneToMany乘法或延迟加载。 -
@RomanPuchkovskiy 是的,这是有道理的。但我还需要能够在加载和编辑后保存实例。所以,它可以工作,但有时我在保存过程中遇到“未找到异常”之类的异常(因为它试图搜索旧集合中的实体)。所以,我决定不使用手动对象构造。但我认为,您的建议应该对只读操作有好处。
标签: java hibernate jpa optimization