#spring #spring-data-jpa #spring-data-rest #spring-hateoas
#spring #spring-data-jpa #spring-данные-остальное #spring-hateoas
Вопрос:
Мы создаем веб-сервис RESTful, аналогичный spring.руководство по вводу-выводу «Доступ к данным JPA с помощью REST«. Чтобы воспроизвести приведенные ниже примеры выходных данных, достаточно добавить отношение ManyToOne к Person следующим образом:
// ...
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
@ManyToOne
private Person father;
// getters and setters
}
Запрос GET после добавления некоторых образцов данных выдает:
{
"firstName" : "Paul",
"lastName" : "Mustermann",
"_links" : {
"self" : {
"href" : "http://localhost:8080/people/1"
},
"father" : {
"href" : "http://localhost:8080/people/1/father"
}
}
}
Но, учитывая, что отец Пола хранится с идентификатором 2, нашим желаемым результатом будет канонический URL для его отношения:
// ...
"father" : {
"href" : "http://localhost:8080/people/2"
}
// ...
Это, конечно, вызовет проблемы, если у некоторых пользователей значение father равно null (ОК, здесь это не имеет особого смысла … ;)), но в этом случае мы бы хотели вообще не отображать ссылку в JSON.
Мы уже пытались реализовать ResourceProcessor для достижения этой цели, но, похоже, к моменту вызова процессора ссылки еще не заполнены. Нам удалось добавить дополнительные ссылки, указывающие на желаемый канонический URL, но не удалось каким-то образом изменить ссылки, добавленные позже.
Вопрос: Существует ли общий подход для настройки генерации ссылок для всех ресурсов?
Чтобы прояснить нашу потребность в канонических URL-адресах: мы используем Javascript-фреймворк SproutCore для доступа к веб-сервису RESTful. Он использует «подобную ORM» абстракцию источников данных, для которых мы внедрили универсальный обработчик выходных данных JSON, создаваемых Spring. При запросе для всех пользователей нам нужно было бы отправить n * (1 q) запросов (вместо просто n) для n пользователей с q отношениями к другим пользователям, чтобы синхронизировать их с источником данных на стороне клиента. Это потому, что «неканоническая» ссылка по умолчанию не содержит абсолютно никакой информации о устанавливаемом отце или идентификаторе отца. Только кажется, что это вызывает огромное количество ненужных запросов, которых можно было бы легко избежать, если бы первоначальный ответ изначально содержал немного больше информации.
Другим решением было бы добавить идентификатор отца к отношению, например:
"father" : {
"href" : "http://localhost:8080/people/1/father",
"id" : 2
}
Ответ №1:
Где-то есть обсуждение, в котором команда Spring Data Rest объяснила, почему свойства отображаются в виде ссылок таким образом. Сказав это, вы все равно могли бы добиться того, что вам нравится, подавив ссылку, сгенерированную SDR, и внедрив ResourceProcessor
. Итак, ваш класс Person будет выглядеть следующим образом. Обратите внимание на аннотацию @RestResource(exported = false)
, чтобы подавить ссылку
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String firstName;
private String lastName;
@ManyToOne
@RestResource(exported = false)
private Person father;
// getters and setters
}
Ваш класс ResourceProcessor будет выглядеть следующим образом
public class EmployeeResourceProcessor implements
ResourceProcessor<Resource<Person>> {
@Autowired
private EntityLinks entityLinks;
@Override
public Resource<Person> process(Resource<Person> resource) {
Person person = resource.getContent();
if (person.getFather() != null) {
resource.add(entityLinks.linkForSingleResour(Person.class, person.getFather().getId())
.withRel("father"));
}
return resource;
}
}
Вышеупомянутое решение работает, только если father
значение с готовностью извлекается вместе с Person
. В противном случае вам нужно иметь свойство fatherId
и использовать его вместо father
property. Не забудьте использовать Jackson @ignore...
для скрытия fatherId
в response JSON.
Примечание: я сам это не тестировал, но предполагаю, что это сработает
Комментарии:
1. я не был уверен, как это сделать без реализации контроллера… это интересная вещь. Обязательно ли регистрировать EmployeeResourceProcessor в качестве компонента?
2. Да, ResourceProcessor должен быть зарегистрирован как компонент. У меня это уже работает. К сожалению, аннотация @RestResource вызывает StackOverflowError (бесконечная рекурсия) — кажется, это применимо не к отдельным атрибутам, а к репозиториям?
3. Да, вам нужно зарегистрировать EmployeeResourceProcessor. Также убедитесь, что установлена последняя версия SDR (2.1.1)
4. Это работает, за исключением
@RestResource(exported = false)
части. Это не делает того, чего вы (ну, я) ожидаете. Вместо того, чтобы скрывать поле от ссылок, оно фактически включает все поля неэкспортированного поля непосредственно в сериализованный объект. Я думаю, вам нужно подавить ссылку в ResourceProcessor
Ответ №2:
Поскольку у меня была такая же проблема, я создал проблему Jira в spring-data-rest:https://jira.spring.io/browse/DATAREST-682
Если достаточное количество людей проголосует за это, возможно, мы сможем убедить некоторых разработчиков реализовать это :-).
Комментарии:
1. Я добавил некоторое объяснение к проблеме: есть другой способ заставить SDR включать канонические ссылки на связанные ресурсы, используя дополнительные проекции: jira.spring.io/browse /…
Ответ №3:
Странно, что вы нажимаете на отображение канонической ссылки. Как только этот ресурс в / father будет извлечен, ссылка на себя должна быть канонической … но на самом деле нет веской причины заставлять отношения отца быть каноническими … может быть, какая-то схема кэширования?
К вашему конкретному вопросу…вы полагаетесь на автоматически сгенерированные контроллеры, поэтому вы отказались от права принимать решения о многих ваших ссылках. Если бы у вас был свой собственный PersonController, вы бы больше отвечали за структуру ссылок. Если бы вы создали свой собственный контроллер, вы могли бы затем использовать EntityLinkshttps://github.com/spring-projects/spring-hateoas#entitylinks с помощью отцовской ID..IE
@Controller
@ExposesResourceFor(Person.class)
@RequestMapping("/people")
class PersonController {
@RequestMapping
ResponseEntity people(…) { … }
@RequestMapping("/{id}")
ResponseEntity person(@PathVariable("id") … ) {
PersonResource p = ....
if(p.father){
p.addLink(entityLinks.linkToSingleResource(Person.class, orderId).withRel("father");
}
}
}
Но, похоже, потребуется много работы, чтобы просто изменить URL
Комментарии:
1. Спасибо за ваш ответ! Чтобы прояснить нашу потребность в канонических URL-адресах: мы используем Javascript-фреймворк SproutCore для доступа к веб-сервису RESTful. Он использует «подобную ORM» абстракцию источников данных, для которых мы внедрили универсальный обработчик выходных данных JSON, создаваемых Spring.
2. Хорошо, я еще не очень хорошо знаком со StackOverlow ;). Приведенный выше комментарий был случайно сохранен нажатием кнопки return, и я пропустил 5-минутный таймфрейм для его редактирования. Я обновлю вопрос, чтобы прояснить нашу потребность в канонических URL-адресах (или аналогичном решении), и мы рассмотрим возможность написания контроллеров для каждого объекта, хотя это кажется громоздким для этой задачи.