JPA 实体图和分页
JPA entity graphs and pagination
在我当前的项目中,我们在系统中有多个搜索页面,我们从数据库中获取大量数据以显示在 UI 中的大 table 元素中。我们使用 JPA 进行数据访问(我们的提供程序是 Hibernate)。大多数页面的数据是从多个数据库 table 收集的——在许多情况下大约有 10 个——包括一些来自 OneToMany 关系的聚合数据(例如 "number of associated entities of type X")。为了提高性能,我们使用 TypedQuery.setFirstResult()
和 TypedQuery.setMaxResults()
的结果集分页,以便在用户滚动 table 时从数据库中延迟加载其他行。由于搜索非常动态,我们使用 JPA CriteriaQuery API 来构建查询。但是,我们目前有点受 N+1 SELECT 问题的困扰。实际上,在某些情况下这很糟糕,因为我们可能会遍历 3 个级别的嵌套 OneToMany 关系,其中每个级别的数据都是延迟加载的。我们不能真正将这些集合声明为实体映射中的预加载,因为我们只对某些页面中的它们感兴趣。 IE。我们可能会从多个不同页面中的相同 table 获取数据,但我们会在不同页面中显示来自 table 和不同关联 table 的不同数据。
为了缓解这个问题,我们开始尝试使用 JPA 实体图,它们似乎对解决 N+1 SELECT 问题有很大帮助。但是,当您使用实体图时,Hibernate 显然会在内存中应用分页。我多少能理解它为什么这样做,但在许多情况下,这种行为否定了实体图的很多(如果不是全部)好处。当我们不使用实体图时,我们可以在不应用任何 WHERE 限制的情况下加载数据(即考虑整个 table 作为结果集),无论 table 有多少百万行,因为由于分页,实际上只获取了非常有限的行。现在分页是在内存中完成的,Hibernate 基本上是获取整个数据库 table(加上实体图中定义的所有关系),然后在内存中应用分页,丢弃其余的行。不好。
所以问题是,有没有一种有效的方法可以使用 JPA (Hibernate) 应用分页和实体图? 如果 JPA 没有提供解决方案,Hibernate-具体扩展也接受table。如果这也不可能,那么其他选择是什么?使用数据库视图?视图会有点麻烦,因为我们支持多个数据库供应商。为不同的供应商创建所有必要的视图会大大增加开发工作量。
我的另一个想法是像我们目前所做的那样同时应用实体图和分页,如果它们 return 太多行,则根本不触发任何查询。我已经需要执行 COUNT 查询以使行的延迟加载在 UI.
中正常工作
我不确定我是否完全理解您的问题,但我们遇到了类似的事情:我们已经分页了可能包含来自多个联合实体的数据的实体列表。这些列表可能会被排序和过滤(其中一些 sorts/filters 必须在内存中应用,因为 dbms 中缺少功能,但这只是一个旁注)并且应该在之后应用分页。
将所有数据保存在内存中效果不佳,因此我们采用了以下方法(可能有 better/more 个标准方法):
- 使用查询加载主要实体的主键(在我们的例子中是简单的 longs)。仅加入排序和过滤所需的内容,使查询尽可能简单。
在我们的例子中,查询实际上会加载更多数据以在必要时在内存中应用排序和过滤器,但数据会尽快释放并且只保留主键。
- 当显示特定页面时,我们提取页面的相应主键并使用第二个查询加载要在该页面上显示的所有内容。第二个查询可能包含更多连接,因此比第 1 步中的查询更复杂、更慢,但由于我们只加载该页面的数据,系统的实际负担很低。
在我当前的项目中,我们在系统中有多个搜索页面,我们从数据库中获取大量数据以显示在 UI 中的大 table 元素中。我们使用 JPA 进行数据访问(我们的提供程序是 Hibernate)。大多数页面的数据是从多个数据库 table 收集的——在许多情况下大约有 10 个——包括一些来自 OneToMany 关系的聚合数据(例如 "number of associated entities of type X")。为了提高性能,我们使用 TypedQuery.setFirstResult()
和 TypedQuery.setMaxResults()
的结果集分页,以便在用户滚动 table 时从数据库中延迟加载其他行。由于搜索非常动态,我们使用 JPA CriteriaQuery API 来构建查询。但是,我们目前有点受 N+1 SELECT 问题的困扰。实际上,在某些情况下这很糟糕,因为我们可能会遍历 3 个级别的嵌套 OneToMany 关系,其中每个级别的数据都是延迟加载的。我们不能真正将这些集合声明为实体映射中的预加载,因为我们只对某些页面中的它们感兴趣。 IE。我们可能会从多个不同页面中的相同 table 获取数据,但我们会在不同页面中显示来自 table 和不同关联 table 的不同数据。
为了缓解这个问题,我们开始尝试使用 JPA 实体图,它们似乎对解决 N+1 SELECT 问题有很大帮助。但是,当您使用实体图时,Hibernate 显然会在内存中应用分页。我多少能理解它为什么这样做,但在许多情况下,这种行为否定了实体图的很多(如果不是全部)好处。当我们不使用实体图时,我们可以在不应用任何 WHERE 限制的情况下加载数据(即考虑整个 table 作为结果集),无论 table 有多少百万行,因为由于分页,实际上只获取了非常有限的行。现在分页是在内存中完成的,Hibernate 基本上是获取整个数据库 table(加上实体图中定义的所有关系),然后在内存中应用分页,丢弃其余的行。不好。
所以问题是,有没有一种有效的方法可以使用 JPA (Hibernate) 应用分页和实体图? 如果 JPA 没有提供解决方案,Hibernate-具体扩展也接受table。如果这也不可能,那么其他选择是什么?使用数据库视图?视图会有点麻烦,因为我们支持多个数据库供应商。为不同的供应商创建所有必要的视图会大大增加开发工作量。
我的另一个想法是像我们目前所做的那样同时应用实体图和分页,如果它们 return 太多行,则根本不触发任何查询。我已经需要执行 COUNT 查询以使行的延迟加载在 UI.
中正常工作我不确定我是否完全理解您的问题,但我们遇到了类似的事情:我们已经分页了可能包含来自多个联合实体的数据的实体列表。这些列表可能会被排序和过滤(其中一些 sorts/filters 必须在内存中应用,因为 dbms 中缺少功能,但这只是一个旁注)并且应该在之后应用分页。
将所有数据保存在内存中效果不佳,因此我们采用了以下方法(可能有 better/more 个标准方法):
- 使用查询加载主要实体的主键(在我们的例子中是简单的 longs)。仅加入排序和过滤所需的内容,使查询尽可能简单。
在我们的例子中,查询实际上会加载更多数据以在必要时在内存中应用排序和过滤器,但数据会尽快释放并且只保留主键。 - 当显示特定页面时,我们提取页面的相应主键并使用第二个查询加载要在该页面上显示的所有内容。第二个查询可能包含更多连接,因此比第 1 步中的查询更复杂、更慢,但由于我们只加载该页面的数据,系统的实际负担很低。