问题。如何避免使用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个查询。
答案 0 :(得分:20)
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来管理Order实例并实现custom controller以实现更复杂的支付流程。除此之外,adds a link to the Order resource指向手动实现的代码。
答案 1 :(得分:6)
如果在Jackson ObjectMapper中配置的序列化程序是通过查看PersistentEntityResource
触发的,那么Spring Data REST将只创建您描述的表示形式,Resource
是一种在Spring内部使用的特殊ResourceProcessor<Resource<MyPojo>>
数据REST。
如果您创建new Resource<MyPojo>(origResource.getContent(), origResource.getLinks())
并返回PersistentEntityResource
,则不会触发默认的Spring Data REST序列化机制,并且将应用Jackson的正常序列化规则。
但请注意,Spring Data REST之所以进行关联的原因是因为在序列化为JSON时,任意停止遍历对象图是非常困难的。通过以它的方式处理关联,它保证了序列化程序不会开始遍历N级深度的对象图,并且在性能和表现的性能上变得更慢。
确保Jackson不会尝试序列化Resource
,这是它在默认配置中所做的事情,将确保不会触发关联的Spring Data REST处理。当然,这方面的缺点是Spring Data REST的助手都不会被触发。如果您仍然需要指向相关资源的链接,则必须确保自己创建这些资源并将其添加到外发平面{{1}}。