我有一个用户。用户有喜欢的橘子、苹果、葡萄和桃子集合。每个葡萄树都有一组葡萄。每个水果都是另一个只有一个字符串字段的实体。我创建了一个拥有每种类型30个喜爱水果的用户,并且每个葡萄树有10个葡萄。所以,在数据库中我有421个实体- 30×4个水果,100×30个葡萄和一个用户。
我的目标是:使用不超过6个SQL查询来加载它们。每个查询不应生成大的结果集(对于这个例子,大于200条记录就算是大结果集了)。
我的理想解决方案如下:
6个请求。第一个请求返回关于用户的信息,结果集大小为1。
第二个请求返回此用户喜欢的苹果的信息,结果集大小为30。
第三、四、五个请求返回与第二个请求相同的信息(结果集大小为30),但是分别返回葡萄树,橘子和桃子的信息。
第六个请求返回所有葡萄树的葡萄信息。
使用fetch join,例如
from User u join fetch u.oranges ...
。这种方式效果很差。结果集大小为30*30*30*30,执行时间为10秒。请求数量=3。我尝试了没有用葡萄的情况,但加上葡萄会使结果集大小增加10倍。只需使用lazy loading即可。在此示例中,这是最好的结果(对于带有@Fetch=SUBSELECT的葡萄)。但在这种情况下,我需要手动遍历每个元素的集合。而且,子选择提取太过全局化,所以我想要一些可以在查询级别上工作的东西。结果集和时间接近理想状态。6个查询和43毫秒。
使用实体图加载。与fetch join相同,但它还会为每个葡萄请求其葡萄藤。然而,结果时间更好(6秒),但仍然很差。请求数量>30。
我尝试通过单独的查询“手动”加载实体来欺骗JPA。像这样:
SELECT u FROM User where id=1; SELECT a FROM Apple where a.user_id=1;
这比lazy loading略差一些,因为对于每个集合,它需要两个查询:第一个查询手动加载实体(我完全控制此查询,包括加载关联实体),第二个查询由Hibernate自己懒加载相同的实体(Hibernate会自动执行此操作)。
执行时间为52,查询数量=10(1个用于用户,1个用于葡萄,4*2个用于每个水果集合)
实际上,“手动”解决方案与SUBSELECT fetch结合使用,允许我使用“简单”的fetch join在一次查询中加载所需实体(例如@OneToOne实体)。因此我打算使用它。但我不喜欢必须执行两个查询来加载集合的方式。
有什么建议吗?
@OneToMany
将第一个和第二个组合,然后按照描述的请求3-6每个执行一个查询,最后在Java代码中组装一个对象。当然,这并不太优雅,但不需要@OneToMany
乘法或懒加载。 - Roman Puchkovskiy