如何在Spring Data Rest中避免n+1查询?

25

问题. 如何在Spring Data REST中避免n+1查询?

背景. 在查询Spring Data REST的资源列表时,每个顶级资源都有与其关联的资源链接,而不是将关联的资源直接嵌入到顶级资源中。例如,如果我查询数据中心的列表,则关联的区域会显示为链接,如下所示:

{
  "links" : [ {
    "rel" : "self",
    "href" : "http://localhost:2112/api/datacenters/1"
  }, {
    "rel" : "datacenters.DataCenter.region",
    "href" : "http://localhost:2112/api/datacenters/1/region"
  } ],
  "name" : "US East 1a",
  "key" : "amazon-us-east-1a"
}

然而,想要在不进行n+1次查询的情况下获取相关信息是非常典型的。以上面的例子为例,我可能想在UI中显示数据中心及其相关地区的列表。

我尝试过的方法。 我在我的RegionRepository上创建了一个自定义查询,以获取给定一组数据中心键的所有地区:

@RestResource(path = "find-by-data-center-key-in")
Page<Region> findByDataCentersKeyIn(
    @Param("key") Collection<String> keys,
    Pageable pageable);

不幸的是,这个查询生成的链接与上面的数据中心查询生成的链接没有重叠。以下是我为定制查询获得的链接:

http://localhost:2112/api/regions/search/find-by-data-center-key-in?key=amazon-us-east-1a&key=amazon-us-east-1b

{
  "links" : [ ],
  "content" : [ {
    "links" : [ {
      "rel" : "self",
      "href" : "http://localhost:2112/api/regions/1"
    }, {
      "rel" : "regions.Region.datacenters",
      "href" : "http://localhost:2112/api/regions/1/datacenters"
    }, {
      "rel" : "regions.Region.infrastructureprovider",
      "href" : "http://localhost:2112/api/regions/1/infrastructureprovider"
    } ],
    "name" : "US East (N. Virginia)",
    "key" : "amazon-us-east-1"
  }, {
    "links" : [ {
      "rel" : "self",
      "href" : "http://localhost:2112/api/regions/1"
    }, {
      "rel" : "regions.Region.datacenters",
      "href" : "http://localhost:2112/api/regions/1/datacenters"
    }, {
      "rel" : "regions.Region.infrastructureprovider",
      "href" : "http://localhost:2112/api/regions/1/infrastructureprovider"
    } ],
    "name" : "US East (N. Virginia)",
    "key" : "amazon-us-east-1"
  } ],
  "page" : {
    "size" : 20,
    "totalElements" : 2,
    "totalPages" : 1,
    "number" : 1
  }
}

挑战似乎在于数据中心查询返回的链接一旦你已经了解数据形状,它们就不是特别有信息量。例如,我已经知道数据中心1的区域位于 /datacenters/1/region,因此如果我想要关于具体涉及哪个区域的实际信息,我必须跟随链接获取它。特别是我必须跟随链接以获取出现在批量查询中的规范URI,这将使我避免进行 n+1 查询。


1
问题不在于REST,它根本不强制要求这种方法。问题在于你将数据模型映射到JSON时,它所表达的信息非常有限;当然不需要这样。 (我更喜欢使用XML查询,因为这样我可以更轻松地发送更丰富的结构;我发现能够区分属性和内容在这里很有帮助。) - Donal Fellows
然而,真正的问题是你只是简单地将数据结构插入到了一个序列化程序中,而没有规划好你实际上想要在每个请求的响应中发送什么信息。我认为你可能需要重新考虑一下这个问题。 - Donal Fellows
我同意你的观点,Donal,关于REST通常的使用方式。然而,这是Spring Data REST框架的工作方式。它基于后端实体定义生成JSON表示形式。当然,我可以重新考虑我的框架选择,但在放弃它之前,我想探索一下我可以做什么。 - user41871
1
然而,我想要看到的是类似于你所提到的东西-至少提供规范URI作为链接,这样我就可以批量查询相关资源,然后通过规范URI连接它们。 - user41871
2个回答

23
Spring Data REST的运作原理如下:默认情况下,我们假定每个应用程序存储库都是REST服务的主资源。因此,如果您公开一个实体的相关对象的存储库,将会为它呈现链接,并且我们会通过嵌套资源(例如foo/{id}/bar)来公开一个实体对另一个实体的分配。
要防止这种情况发生,请使用@RestResource(exported = false)注释相关的存储库接口,这将防止由此存储库管理的实体成为顶级资源。
更一般的方法是从Spring Data REST开始让您公开要管理的资源并应用默认规则。然后,您可以通过实现ResourceProcessor<T>并将其注册为Spring bean来自定义呈现和链接。 ResourceProcessor 然后允许您自定义渲染数据、添加到表示中的链接等等。
对于其他所有内容,请手动实现控制器(可能与默认控制器的URI空间混合),并通过ResourceProcessor实现向它们添加链接。这方面的示例可在Spring RESTBucks示例中看到。该示例项目使用Spring Data REST管理订单实例,并实现了一个自定义控制器来实现更复杂的支付过程。除此之外,它向订单资源添加链接以指向手动实现的代码。

谢谢Oliver。我希望参与其中的实体成为顶层资源,并彼此建立关系。我查看了ResourceProcessor,但它的process()方法以Resource作为参数,而且我不知道如何在没有使用其非规范URL获取资源的情况下添加相关联的资源的规范URL。 - user41871
具体点说,我想显示一个包含20个数据中心及其相关地区的表格。每个数据中心都有一个链接,例如:/datacenter/{id}/region。如何获得这些地区而不用单独调用每个/datacenter/{id}/region呢?我认为无法将Region的规范URL作为链接添加,因为DataCenter链接到Region的信息根本不可用。希望这能澄清我所面临的问题。 - user41871
2
请注意,这不是一次性的。我的大多数顶级资源(有几十个——这是一个配置管理系统)都有我想在列表视图中显示的关联。那么,我的想法是在所有这些情况下编写自定义控制器吗? - user41871
2
你可以让 ResourceProcessor 使用存储库查找相关区域并将它们添加到包装的数据中心实例中。另一个选项是在 RegionRepository 上公开查找器,以检索与数据中心相关的所有区域,并从客户端触发该操作。 - Oliver Drotbohm

8
Spring Data REST仅在Jackson ObjectMapper内配置的序列化程序通过检测到PersistentEntityResource时触发,该对象是Spring Data REST内部使用的一种特殊类型的Resource。
如果创建ResourceProcessor<Resource<MyPojo>>并返回new Resource<MyPojo>(origResource.getContent(), origResource.getLinks()),则默认的Spring Data REST序列化机制将不会被触发,而会应用Jackson的普通序列化规则。
但是请注意,Spring Data REST之所以以这种方式处理关联,是因为在序列化为JSON时任意停止遍历对象图形非常困难。通过以这种方式处理关联,它保证了序列化器不会开始遍历N级深度的对象图形,提高了性能和传递的表现性能。
确保Jackson不尝试序列化PersistentEntityResource(这是默认配置中正在执行的操作)将确保不会触发Spring Data REST处理关联的任何功能。当然,缺点是将不会触发Spring Data REST的辅助程序。如果您仍想要与相关资源的链接,请确保自己创建并将其添加到传出的纯Resource中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接