1

在我当前的项目中,我们在系统中有多个搜索页面,我们从数据库中获取大量数据以显示在 UI 中的大表格元素中。我们使用 JPA 进行数据访问(我们的提供者是 Hibernate)。大多数页面的数据是从多个数据库表中收集的——在许多情况下大约有 10 个——包括来自 OneToMany 关系的一些聚合数据(例如“类型 X 的关联实体的数量”)。为了提高性能,我们使用结果集分页TypedQuery.setFirstResult()TypedQuery.setMaxResults()当用户滚动表格时,延迟加载数据库中的其他行。由于搜索非常动态,我们使用 JPA CriteriaQuery API 来构建查询。但是,我们目前在某种程度上受到 N+1 SELECT 问题的困扰。实际上在某些情况下这很糟糕,因为我们可能会迭代 3 层嵌套的 OneToMany 关系,其中每一层的数据都是延迟加载的。我们不能真正将这些集合声明为在实体映射中预先加载,因为我们只在某些页面中对它们感兴趣。即,我们可能从几个不同页面中的同一个表中获取数据,但我们在不同页面中显示来自表和不同关联表的不同数据。

为了缓解这个问题,我们开始尝试使用 JPA 实体图,它们似乎对 N+1 SELECT 问题有很大帮助。但是,当您使用实体图时,Hibernate 显然会在内存中应用分页。我可以在一定程度上理解它为什么会这样做,但是在许多情况下,这种行为否定了实体图的很多(如果不是全部)好处。当我们不使用实体图时,我们可以在不应用任何 WHERE 限制的情况下加载数据(即,将整个表视为结果集),无论该表有几百万行,因为只有非常有限的行数实际上是由于分页而被提取的。现在分页是在内存中完成的,Hibernate 基本上会获取整个数据库表(加上实体图中定义的所有关系),然后在内存中应用分页,扔掉其余的行。不好。

所以问题是,是否有一种有效的方法可以使用 JPA(Hibernate)同时应用分页和实体图?如果 JPA 没有为此提供解决方案,那么特定于 Hibernate 的扩展也是可以接受的。如果这也不可能,那么其​​他选择是什么?使用数据库视图?视图会有点麻烦,因为我们支持多个数据库供应商。为不同的供应商创建所有必要的视图会大大增加开发工作量。

我的另一个想法是像我们目前一样应用实体图和分页,如果它们返回太多行,则根本不触发任何查询。我已经需要执行 COUNT 次查询以使行的延迟加载在 UI 中正常工作。

4

1 回答 1

3

我不确定我是否完全理解您的问题,但我们遇到了类似的情况:我们已经分页的实体列表可能包含来自多个连接实体的数据。这些列表可能会被排序和过滤(其中一些排序/过滤器必须在内存中应用,因为 dbms 中缺少功能,但这只是一个旁注),然后应该应用分页。

将所有数据保存在内存中效果不佳,因此我们采用了以下方法(可能有更好/更标准的方法):

  1. 使用查询来加载主要实体的主键(在我们的例子中是简单的 long)。仅加入排序和过滤所需的内容,以使查询尽可能简单。

    在我们的例子中,查询实际上会加载更多数据,以便在必要时在内存中应用排序和过滤器,但这些数据会尽快释放,并且只保留主键。
  2. 在显示特定页面时,我们提取页面的相应主键并使用第二个查询来加载要在该页面上显示的所有内容。第二个查询可能包含更多连接,因此比第 1 步中的更复杂和更慢,但由于我们只为该页面加载数据,因此系统的实际负担非常低。
于 2016-01-13T11:04:19.037 回答