【问题标题】:Hibernate pagination of sorted data with joins使用连接对已排序数据进行休眠分页
【发布时间】:2017-06-15 17:22:26
【问题描述】:

我们目前正在研究加快应用程序速度的方法,其中很大一部分与实体列表(实际上是表格)有关。

参数和要求

该列表的参数和要求如下(我将尝试仅在此处提及相关的):

  • 列表中最多可以有 500,000 行/实体。
  • 一次只显示其中的几个,我们将在此处使用分页。
  • 用户可以选择要在列表中显示哪些列(因此我们无法提供单个“静态”查询)。
  • 这些列表列中的大多数都是可排序和/或可过滤的。
  • 实体具有一对多关系,它们也提供一些列表列。
  • 其中一些列表列可以包含多个值(在单个单元格中显示为列表)
  • 任何源自当前用户操作(例如编辑、上传等)的数据更新都应尽快反映在列表中(即“立即”)

为了使模型更清晰,请考虑以下简化实体(如 JPA 中的)模型:

class Car {
  String manufacturer;
  String model;
  Date dateOfProduction;
  List<TyreSize> allowedTyreSizes;
  Set<Date> inspectionDates;
}

请不要试图在该模型中赋予太多含义,因为它只是为了说明问题(我们的数据不同且复杂得多)。

汽车的“完整”列表可能如下所示:

+==============+=======+=======+===============+=============+
| Manufacturer | Model | Prod. | Allowed Tyres | Inspections |
+==============+=======+=======+===============+=============+
| BMW          | 320d  |01/2016| - 225/40 R18  | - 01/07/16  |
|              |       |       | - 225/45 R17  | - 13/12/16  |
+--------------+-------+-------+---------------+-------------+
| Toyota       | Camry |09/2016| - 185/70 R13  | - 31/12/16  |
+--------------+-------+-------+---------------+-------------+ 

由于用户可以在运行时选择要显示的列,我们正在动态构建必要的查询。到目前为止,这一切都很好。

基本问题

我们遇到的问题是涉及排序和过滤时的性能:我们目前的方法是将排序和过滤所需的所有数据加载到内存中,在那里进行排序和过滤,然后保留排序后的 id 和页面列表那些。我们知道这有点慢,但到目前为止,性能足以满足我们的管理层。不过,由于我们现在有更多数据可供操作,而且性能要求也提高了,因此情况发生了变化。

因此,我们正在研究改进所有数据的排序和过滤的方法,而我们目前正在遵循在数据库上执行此操作的方法,我仍然会问这个(侧面)问题:

  • 如何最好地处理具有动态列和每个单元格可能有多个值的大量行的分页?

目前我们正在使用 Postgresql,如果可能的话希望继续使用它,但如果不同的存储更适合我们至少会检查一下。

目前的方法和问题 (在底部)

如上所述,我们目前正在尝试让数据库对我们的数据进行排序、过滤和分页。可以使用 2 个查询:一个用于获取当前页面的行 ID,另一个用于实际加载这些行的数据。

由于挑战是第一个查询,我将专注于:

AFAIK 我们可以在 SQL 中执行类似的操作(使用上面的汽车示例):

SELECT DISTINCT id FROM (
  SELECT id, ... FROM car c
    LEFT OUTER JOIN allowedtyresizes ats ON c.id = ats.car_id
    LEFT OUTER JOIN tyresizes ts ON ts.id = ats.tyresize_id
    ... //additional joins if required
    ORDER BY ... //apply any user-defined sorts
    WHERE ... //apply any user-defined filters (or maybe put them into the joins)
  )
  OFFSET ... //page offset
  LIMIT ...  //page size

理论上,这个查询(虽然它可能不完全正确)应该提供我们需要的结果,以确定要为当前页面加载哪些行。

由于我们使用的是 Hibernate (5.2 atm),我们希望使用 HQL 或 Criteria 来实现这一点。然而,Hibernate 似乎不支持从上面的 select 语句中进行选择,因此这可能不是一种可行的方法。如果我们不得不回退到原生 SQL 或完全不同的方法,那就这样吧,但我们更愿意让它与现有的基础设施一起工作。

所以问题是:

  • Hibernate 5.x 是否支持“从选择中选择”之类的功能?
  • 当必须对多对关系进行排序和过滤时,如何使用 Hibernate 进行分页,即单个连接可能导致重复行?

【问题讨论】:

  • 您使用 HQL 运行 SQL 查询,方法是将查询作为字符串传递给它
  • @GauravSrivastav 是的,我们知道如何执行 SQL 查询,如果没有更好的方法,我们将使用它。然而,由于查询可能变得相当复杂并且是动态创建的,我们宁愿不必访问我们实体的映射信息来提取构建此类查询所需的信息 - 如果 Hibernate 已经提供了我们可以使用的东西,则不会。
  • 您的模型有多复杂?通过使用数据库视图并将实体绑定到它们,您可能会获得最佳性能。坏事是您需要手写数据库视图(如果您开始支持新的 RDBMS,可能还有另一个版本)。好消息是实体会变得更简单,Hibernate 生成性能不佳的 SQL 的机会也会减少。
  • @MickMnemonic 该模型非常复杂,有 10 多个表可能会在多个级别上连接(例如实体->类别->文本)和一些可以在运行时创建/添加的数据列并且在某种程度上仿照entity abstraction pattern(被某些人认为是反模式,仅用于实际需要它的部分)。
  • @MickMnemonic 视图可能是我们模型的主要部分的一个选项,它的结构在运行时是静态的。你将如何处理潜在的多对多关系?它们会以某种方式被建模为数组列吗?

标签: java hibernate pagination


【解决方案1】:

我以前的雇主也有类似的要求,我们同样注意到,在较小的数据集下,数据库完全有能力做到这一点;但是,会有一个临界点,甚至数据库也会受到影响。

我的解决方案是引入 Hibernate Search 及其与 ElasticSearch 的集成,以将搜索数据存储在 NoSQL Lucene 数据存储中,正如您所描述的,这对于基于 unicode 的文本查询和排序来说绝对是快速的。

这使您可以继续使用已有的 Hibernate ORM 基础架构,并以最小的努力将这些附加组件融入您的架构中。集成是无缝的,当然值得投资,尤其是在您的数据集随着时间不断增长的情况下。

我们处理了数以千万计的行,绝对没有性能问题。执行排序和分页的查询平均花费不到 100 毫秒。

【讨论】:

  • 感谢您的信息。我们实际上已经有了 Hibernate Search,只是直接使用了 Lucence 而不是 Elastic。 Elastic 会额外提高性能还是只是让访问数据更容易?您如何处理更新 - 您是整体更新更改的文档还是仅更新已更改的字段(如果可能的话)?我也猜想更新是异步处理的,不是吗?
  • 我发现使用 ES 更容易,因为索引托管在更多的微服务架构中,而不是将 Lucene 直接嵌入到应用程序中。我们在主/从之间的索引复制方面也遇到了一些问题,这导致我们改用 ES。至于更新,它们被复制到 ES,就像它们今天被复制到 Lucene 一样。我相信更新不是异步的,而是在 Hibernate 引发 Search 侦听的各种事件时发生。
  • Afaik Hibernate Search 在事务结束时触发更新,所以是的,默认情况下它们基本上是同步的。但是(在另一个项目中)我们遇到了一些问题,因为较大的事务需要我们不时地清除一级缓存,这导致 HS 阻塞分离的实体。因此我们在那里引入了一些异步更新,因此我的问题是(如果 ES/Lucene 不可用,异步更新也会降低事务回滚的风险)。
  • 也许可以,但是您可以确保在代理后面设置 ES,这样您就可以保证代理已启动,并且您的 ES 节点在集群中运行,这样至少有一个实例始终启动并且可用于冗余目的。
  • 嗯,这可能会降低风险。但是,即使使用同步更新,ES 故障也可能根本不是问题,因为实际数据和 ES 之间的一致性可能非常重要。我目前正试图让我们的用户体验专家和/或项目管理人员进行检查。 :)
猜你喜欢
  • 1970-01-01
  • 2012-07-01
  • 1970-01-01
  • 2012-02-19
  • 2023-04-05
  • 2015-11-06
  • 2010-12-27
  • 1970-01-01
  • 2012-09-06
相关资源
最近更新 更多