【问题标题】:How do I avoid n+1 queries with Spring Data Rest?如何避免使用 Spring Data Rest 进行 n+1 查询?
【发布时间】:2013-03-30 23:53:03
【问题描述】:

问题。如何避免使用 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 查询。

【问题讨论】:

  • 问题不在于 REST,它根本不需要这种方法。问题是您映射到 JSON 的数据模型在任何时候都相当稀疏;它当然不需要那样。 (我更喜欢使用 XML 查询,因为这样我可以更轻松地返回更丰富的结构;我发现能够区分属性和内容在这里很有帮助。)
  • 不过,真正的问题是您只是将数据结构放入序列化程序中,而不是计划您实际想要发回的信息以响应每个请求。我认为您可能希望重新访问。
  • 同意你的看法 Donal re:一般是 REST。然而,为了澄清,这是 Spring Data REST 框架的工作方式。 (它基于后端实体定义生成 JSON 表示。)当然我可以重新审视我选择的框架,但我想在放弃它之前探索我能用它做什么。
  • 我希望看到的是与您提到的内容类似的内容——至少提供规范 URI 作为链接,以便我可以批量查询相关资源,然后通过规范 URI 连接它们。

标签: spring rest spring-data spring-data-rest spring-hateoas


【解决方案1】:

Spring Data REST 像这样工作的原因如下:默认情况下,我们假设每个应用程序存储库都是 REST 服务的主要资源。因此,如果您为实体的相关对象公开一个存储库,您将获得呈现给它的链接,并且我们通过嵌套资源(例如foo/{id}/bar)公开一个实体对另一个实体的分配。

为防止这种情况发生,请使用@RestResource(exported = false) 注释相关的存储库接口,以防止此存储库管理的实体成为顶级资源。

更通用的方法是从 Spring Data REST 开始,让您公开想要管理的资源并应用默认规则。然后,您可以通过实现 ResourceProcessor&lt;T&gt; 并将您的实现注册为 Spring bean 来自定义呈现和链接。然后ResourceProcessor 将允许您自定义呈现的数据、添加到表示的链接等。

对于其他一切,手动实现控制器(可能混合到默认控制器的 URI 空间中)并通过 ResourceProcessor 实现添加指向这些控制器的链接。在Spring RESTBucks 示例中可以看到一个示例。示例项目使用 Spring Data REST 来管理 Order 实例并实现 custom controller 来实现更复杂的支付流程。除此之外,adds a link to the Order resource 指向手动实现的代码。

【讨论】:

  • 谢谢奥利弗。我希望所涉及的实体是具有它们之间关系的顶级资源。我查看了 ResourceProcessor,但它的 process() 方法将 Resource 作为参数,我不知道如何在不首先使用其非规范 URL 获取资源的情况下添加关联的 Resource 的规范 URL。
  • 具体来说:我想显示一个包含 20 个数据中心及其相关区域的表格。每个数据中心都有这样的链接:/datacenter/{id}/region。如何在不单独调用每个 /datacenter/{id}/region 的情况下获取区域?我不认为我可以将区域的规范 URL 添加为链接,因为有关区域的信息在数据中心到区域的链接中根本不可用。希望这能澄清我面临的问题。
  • 请注意,这不是一次性的。我的大多数顶级资源(有几十个——这是一个配置管理系统)都有我希望能够在列表视图中显示的关联。是否认为我会在所有这些情况下编写自定义控制器?
  • 您可以让ResourceProcessor 使用存储库查找关联区域并将它们添加到包装的数据中心实例。另一种选择是在RegionRepository 上公开一个查找器,以检索数据中心的所有区域并从客户端触发。
【解决方案2】:

只有在 Jackson ObjectMapper 中配置的序列化程序通过看到 PersistentEntityResource(这是 Spring Data REST 中使用的一种特殊类型的 Resource)触发时,Spring Data REST 才会创建您描述的表示。

如果您创建 ResourceProcessor&lt;Resource&lt;MyPojo&gt;&gt; 并返回 new Resource&lt;MyPojo&gt;(origResource.getContent(), origResource.getLinks()),则不会触发默认的 Spring Data REST 序列化机制,将应用 Jackson 的正常序列化规则。

但是请注意,Spring Data REST 以它的方式进行关联的原因是因为在序列化为 JSON 时很难任意停止遍历对象图。通过以它的方式处理关联,它可以保证序列化程序不会开始遍历 N 级深度的对象图,并且不会在性能和通过网络传输的表示的性能方面变得更慢。

确保 Jackson 不会尝试序列化 PersistentEntityResource,这是它在默认配置中所做的,将确保不会触发任何 Spring Data REST 关联处理。当然,不利的一面是 Spring Data REST 的任何助手都不会被触发。如果您仍需要相关资源的链接,则必须确保自己创建这些链接并将它们添加到传出的普通 Resource

【讨论】:

    猜你喜欢
    • 2017-11-06
    • 1970-01-01
    • 1970-01-01
    • 2020-09-28
    • 1970-01-01
    • 1970-01-01
    • 2015-03-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多