Подтвердить что ты не робот

Как избежать n + 1 запросов с помощью Spring Data Rest?

Вопрос. Как избежать n + 1 запросов с помощью Spring Data REST?

Фон. При запросе 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 запросов. Чтобы придерживаться приведенного выше примера, мне может потребоваться отобразить список центров данных и связанных с ними областей в пользовательском интерфейсе.

Что я пробовал. Я создал собственный запрос на моем 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 запросов.

4b9b3361

Ответ 1

Причина 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, чтобы указать на введенный вручную код.

Ответ 2

Spring Data REST создаст только представление, которое вы описываете, если сериализатор, настроенный внутри Jackson ObjectMapper, запускается, увидев PersistentEntityResource, который является специальным типом Resource, который используется внутри Spring Data REST.

Если вы создадите ResourceProcessor<Resource<MyPojo>> и вернете a new Resource<MyPojo>(origResource.getContent(), origResource.getLinks()), тогда не будет запущено машинное сериализационное устройство по умолчанию Spring Data REST и будут применяться нормальные правила сериализации Джексона.

Обратите внимание, однако, что причина Spring Data REST делает ассоциации так, как это происходит, потому что очень сложно произвольно остановить перемещение графа объекта при сериализации в JSON. Управляя ассоциациями так, как это делается, он гарантирует, что сериализатор не начнет перемещаться по графу объектов, который имеет N уровней в глубину и станет намного медленнее по производительности и производительности представления, проходящего через провод.

Обеспечение того, что Джексон не пытается сериализовать PersistentEntityResource, что он делает в конфигурации по умолчанию, гарантирует, что ни одна из Spring Data REST обработки ассоциаций не запускается. С другой стороны, это означает, что ни один из Spring Data REST-помощников не будет запущен. Если вам все еще нужны ссылки на связанные ресурсы, вам необходимо убедиться, что вы сами их создаете и добавьте их в исходящую обычную Resource.