Как восстановить отношения сущностей после сохранения?

Я разрабатываю веб-сервис RESTful с spring-data в качестве уровня доступа к данным, поддерживаемым JPA/ Спящий режим. Очень часто возникают отношения между объектами домена. Например, представьте сущность Product, которая имеет сущность Category.

Теперь, когда клиент POSTs Product представляет метод JAX-RS. Этот метод снабжен аннотацией @Transactional для переноса каждой операции репозитория в транзакцию. Конечно, клиент отправляет только id уже существующего Category, а не полное представление, а только ссылку (внешний ключ).

В этом методе, если я сделаю это:

entity = repository.save(entity);

переменная entity теперь имеет Category только с набором полей id. Это меня не удивило. Я не ожидал, что при сохранении (вставка SQL) будет получена информация о связанных объектах. Но мне нужно, чтобы весь объект Product и связанные с ним объекты могли вернуться к пользователю.

Затем я сделал это:

entity = repository.save(entity);
entity = repository.findOne(entity.getId());

то есть получить объект после его сохранения в рамках той же транзакции/сеанса.

К моему удивлению, переменная entity ничего не изменила. На самом деле база данных даже не получила ни одного запроса на выборку. Это связано с кешем Hibernate. По какой-то причине в той же транзакции поиск не извлекает весь граф объектов, если этот объект ранее сохранялся.

С Hibernate решение, по-видимому, заключается в использовании session.refresh(entity) (см. внутри одного и того же">это и это) . Имеет смысл.

Но как мне добиться этого с помощью весенних данных?

Я хотел бы избежать создания повторяющихся пользовательских репозиториев. Я думаю, что эта функциональность должна быть частью самих весенних данных (некоторые люди уже сообщали об этом на форуме весенних данных: thread1, thread2).


person miguelcobain    schedule 29.01.2013    source источник


Ответы (1)


тл;др

Ссылки между объектами в веб-слое должны быть явными с помощью ссылок и не должны быть скрыты за частично заполненными экземплярами объектов. Ссылки на уровне постоянства представлены ссылками на объекты. Таким образом, должен быть специальный шаг, преобразующий один (ссылку) в другой (полностью заполненную ссылку на объект).

Подробнее

Это анти-шаблон - передавать идентификаторы бэкэнда как таковые и предполагать, что маршалинговая привязка делает правильные вещи. Таким образом, клиенты должны скорее работать со ссылками и передавать их серверу, чтобы указать, что они хотят установить соединение между уже существующим ресурсом и ресурсом, который будет создан.

Итак, предполагая, что у вас есть существующий Category, выставленный через /categories/4711, вы можете опубликовать на своем сервере:

POST /products
{ links : [ { rel : "category", href : "/categories/4711" } ],
  // further product data
}

Сервер создаст новый экземпляр Product, заполнит его дополнительными данными и, в конечном итоге, заполнит ассоциации следующим образом:

  1. Определите свойства, которые необходимо заполнить, просмотрев типы отношений ссылок (например, свойство category здесь.
  2. Извлечь идентификатор бэкэнда из заданного URI.
  3. Используйте соответствующий репозиторий для поиска экземпляра связанной сущности.
  4. Установите его на корневой объект

Итак, в вашем примере сводится к:

Product product = new Product();
// populate primitive properties
product.setCategory(categoryRepository.findOne(4711));
productRepository.save(product);

Просто отправьте что-то вроде этого на сервер:

POST /products
{ category : {
    id : 1, … },
  … 
}

является неоптимальным по многим причинам:

  1. Вы хотите, чтобы поставщик постоянства неявно сохранял экземпляр Product и в то же время «признавал», что упоминаемый экземпляр Category (фактически состоящий только из идентификатора) не предназначен для сохранения, а обновляется данными уже существующего Category? Я бы сказал, что это немного магии.
  2. По сути, вы накладываете структуру данных, которую вы используете для POST на сервер, на уровень сохраняемости, ожидая, что он будет прозрачно обрабатывать то, как вы решили выполнять POST. Это не ответственность уровня сохраняемости, а веб-уровня. Вся цель веб-уровня состоит в смягчении характеристик протокола на основе HTTP с использованием представлений и ссылок на серверную службу.
person Oliver Drotbohm    schedule 29.01.2013
comment
Отличный ответ! Разве мы уже не передаем идентификаторы бэкэнда в URL-адресах одного ресурса, например /categories/4711? - person miguelcobain; 29.01.2013
comment
Кроме того, нам нужно проанализировать URL-адрес связанной категории и получить ее идентификатор. Это должно быть предоставлено каким-либо помощником или библиотекой HATEOAS. Не уверен, как я могу сделать это элегантно. HATEOAS кажется более обширным, чем я думал. :) - person miguelcobain; 29.01.2013
comment
Я использовал /categories/4711 в качестве удобочитаемого примера, но URI может быть /fe6325a27, что, я думаю, делает необходимость в дополнительном шаге сопоставления более очевидной. В Spring HATEOAS у нас уже есть API для создания Link экземпляров, указывающих на методы контроллера и ресурсы JAX-RS. . К сожалению, вы единственный, кто знает, где в URI вы найдете идентификатор бэкэнда (например, /categories/4711 VS. /categories?id=4711. Может потребоваться даже полный шаг сопоставления, как показано выше. Поэтому сложно создать общую поддержку для этого. - person Oliver Drotbohm; 29.01.2013
comment
Я вижу, что вы являетесь участником весеннего хатеоа. Можете ли вы сказать мне, насколько хорошо он поддерживает JAX-RS, особенно в отношении ResourceAssemblers? Есть ли какой-нибудь пример с JAX-RS? У меня возникают проблемы с JaxRsLinkBuilder, когда он пытается выполнить new JaxRsLinkBuilder(ServletUriComponentsBuilder.fromCurrentServletMapping()). Я получаю Could not find current request via RequestContextHolder. Я пытался использовать эту библиотеку в прошлом. Похоже, он очень хорошо обращается к HATEOAS, но так и не смог заставить его работать с JAX-RS. Только что нашел пример репозитория с обычными контроллерами Spring - person miguelcobain; 30.01.2013
comment
Я не использовал последние весенние хатеоа. Я не использовал EntityLinks. Можно ли @EnableEntityLinks в xml? Я немного не понимаю, как использовать программную конфигурацию в приложении Spring + JAX-RS. Какой WebInitializer должен быть подклассом/реализован? Может быть, мне следует создать новый вопрос SO. - person miguelcobain; 30.01.2013
comment
Кратко: пока нет поддержки XML, лучше всего использовать JavaConfig. Дальнейшие вопросы SO, вероятно, являются хорошей идеей. Как правило, вы можете взглянуть на Spring RESTBucks, чтобы увидеть, как это может работать… github.com/olivergierke/ весенние рестбаксы - person Oliver Drotbohm; 31.01.2013
comment
@OliverDrotbohm 1) Я думал, что ненависть links является только частью ответа GET и не должна быть частью POST. Я никогда не думал об этом. 2) Какие преимущества я бы получил, если бы использовал DTO и для каждой дочерней ассоциации мне нужно было бы делать отдельный вызов. Тогда весь смысл DTO (сохранение вызовов сервера) теряется. - person TheCoder; 03.02.2020