【问题标题】:Hibernate: initialization of complex objectHibernate:复杂对象的初始化
【发布时间】: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) 来实现。

我尝试了以下方法:

  1. 使用 fetch join,例如from User u join fetch u.oranges ...。这太可怕了。结果集为 30*30*30*30,执行时间为 10 秒。请求数 = 3。我在没有葡萄的情况下尝试过,用葡萄你会得到 x10 大小的结果集。

  2. 只需使用延迟加载。这是本示例中的最佳结果(使用 @Fetch= SUBSELECT 用于葡萄)。但在那种情况下,我需要手动迭代每个元素集合。此外,subselect fetch 的设置过于全局,所以我想要一些可以在查询级别上工作的东西。结果集和时间接近理想。 6 个查询和 43 毫秒。

  3. 加载实体图。与 fetch join 相同,但它也请求每个葡萄获取它的葡萄藤。然而,结果时间更好(6秒),但仍然很糟糕。请求数 > 30。

  4. 我试图通过在单独的查询中“手动”加载实体来欺骗 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


【解决方案1】:

我通常通过对实体和集合使用batch fetching 来涵盖 99% 的此类用例。如果您在读取它们的同一事务/会话中处理获取的实体,那么您不需要做任何额外的事情,只需导航到处理逻辑所需的关联,生成的查询将非常优化。如果您想以分离的形式返回获取的实体,则手动初始化关联:

User user = entityManager.find(User.class, userId);
Hibernate.initialize(user.getOranges());
Hibernate.initialize(user.getApples());
Hibernate.initialize(user.getGrapevines());
Hibernate.initialize(user.getPeaches());
user.getGrapevines().forEach(grapevine -> Hibernate.initialize(grapevine.getGrapes()));

请注意,最后一个命令不会实际上对每个小道消息执行查询,因为在初始化第一个集合时会初始化多个 grapes 集合(直到指定的 @BatchSize)。您只需迭代所有这些以确保所有这些都已初始化。

这种技术类似于您的手动方法,但效率更高(不会对每个集合重复查询),并且在我看来更具可读性和可维护性(您只需调用 Hibernate.initialize 而不是手动编写 Hibernate 自动生成的相同查询)。

【讨论】:

    【解决方案2】:

    我将建议另一个关于如何在 Grapevine 中懒惰地获取 Grapes 集合的选项:

    @OneToMany
    @BatchSize(size = 30)
    private List<Grape> grapes = new ArrayList<>();
    

    与其进行子选择,不如使用in (?, ?, etc) 一次获取多个Grapes 集合。相反,? Grapevine ID 将被传递。这与一次查询 1 个List&lt;Grape&gt; 集合相反。

    这只是你的武器库的另一种技术。

    【讨论】:

    • 谨慎使用,如果你已经加载了 30 个Gravevines,但只对其中一个的Grapes 感兴趣,加载那些Grapes 也会导致加载Grapes 对于其他 29 个 Grapevines.. 如果您要查看的一个有 3 个 Grapes,而另一个有 100,000 个,那么您最终会加载比实际需要的多得多的内容。
    【解决方案3】:

    我不太明白你在这里的要求。在我看来,您希望 Hibernate 做一些它不打算做的事情,而当它做不到时,您想要一个远非最佳的破解解决方案。为什么不放松限制并获得有效的东西呢?为什么你一开始就有这些限制?

    一些通用指针:

    1. 使用 Hibernate/JPA 时,您无法控制查询。你也不应该这样做(除了少数例外)。有多少查询,它们执行的顺序等等,几乎是你无法控制的。如果您想完全控制查询,只需跳过 JPA 并改用 JDBC(例如 Spring JDBC。)
    2. 了解延迟加载是在此类情况下做出决策的关键。在获取拥有实体时获取延迟加载的关系,而是在实际使用时返回数据库并获取它们。这意味着如果您不每次都使用该属性,延迟加载会有所回报,但会在您实际使用它的次数时受到惩罚。 (Fetch 连接用于急切获取惰性关系。实际上并不适用于从数据库进行常规加载。)
    3. 使用 Hibernate 进行查询优化不应该是您的第一道防线。始终从您的数据库开始。是否使用主键和外键、范式等正确建模?您是否在适当的位置(通常在外键上)有搜索索引?
    4. 在非常有限的数据集上测试性能可能不会得到最好的结果。连接等方面的开销可能会大于实际运行查询所花费的时间。此外,可能会出现花费几毫秒的随机中断,这会产生可能具有误导性的结果。
    5. 查看代码的小提示:永远不要为实体中的集合提供设置器。如果实际在事务中调用,Hibernate 将抛出异常。
    6. tryManualLoading 可能比您想象的要多。首先,它获取用户(使用延迟加载),然后获取每个水果,然后通过延迟加载再次获取水果。 (除非 Hibernate 知道查询将与延迟加载时相同。)
    7. 您实际上不必遍历整个集合来启动延迟加载。您可以这样做user.getOranges().size()Hibernate.initialize(user.getOranges())。但是对于葡萄藤,您必须进行迭代以初始化所有葡萄。

    有了正确的数据库设计,并在正确的地方进行延迟加载,除了:

    em.find(User.class, userId);
    

    如果延迟加载需要大量时间,则可能是连接获取查询。

    根据我的经验,加速 Hibernate 的最重要因素是在数据库中搜索索引

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-09-21
      • 2015-06-09
      • 2012-01-10
      • 1970-01-01
      • 2012-01-17
      • 2021-12-29
      相关资源
      最近更新 更多