JPA 实体图和分页

Mar*_*ölä 1 java pagination hibernate jpa entitygraph

在我当前的项目中,我们的系统中有多个搜索页面,我们从数据库中获取大量数据,并显示在 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 中正常工作。

Tho*_*mas 6

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

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

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

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