Ресурс репозитория не может встраивать объект, ссылающийся на себя
У меня есть две сущности, Store и ProductCategory, которые я сохраняю и хочу получить из JpaRepository, аннотированного @RepositoryRestResource. Магазины имеют коллекцию категорий товаров, а категории товаров связаны с родителями / детьми. Кроме того, я хотел бы встроить иерархию категорий продуктов, когда GET-запрос сделан в хранилище REST Магазина.
Сущности выглядят следующим образом:
Хранить:
@Entity
@Table(name = "store")
@EqualsAndHashCode(exclude="categories", callSuper = true)
@ToString(exclude="categories", callSuper = true)
public class Store extends BaseEntity {
@Getter
@Setter
@Column(name = "name")
private String name;
@OneToMany(targetEntity = ProductCategory.class, cascade = CascadeType.ALL, mappedBy = "store")
@Getter
private List<ProductCategory> categories = new ArrayList<>();
public void setCategories(List<ProductCategory> categories) {
for (ProductCategory category : categories) {
if (category.getStore() == null || !category.getStore().equals(this)) {
category.setStore(this);
}
}
this.categories = categories;
}
}
ProductCategory.java:
@Entity
@Table(name = "product_category")
@EqualsAndHashCode(exclude={"store", "children", "parent"}, callSuper = true)
@ToString(exclude={"store", "children", "parent"}, callSuper = true)
public class ProductCategory extends BaseEntity {
@Getter @Setter
@Column(name="name")
private String name;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="store_id", nullable = true)
@Getter
@JsonIgnore
@RestResource(exported = false)
private Store store;
@ManyToOne(fetch=FetchType.LAZY)
@JoinColumn(name="parent", nullable = true)
@Getter
@JsonIgnore
@RestResource(exported = false, path = "parent")
private ProductCategory parent;
@OneToMany(targetEntity = ProductCategory.class, cascade=CascadeType.ALL, mappedBy="parent")
@Getter
@RestResource(rel = "children", path = "children")
private List<ProductCategory> children = new ArrayList<>();
public void setStore(Store store) {
this.store = store;
if(!store.getCategories().contains(this)) {
store.getCategories().add(this);
}
}
public void setParent(ProductCategory parent) {
this.parent = parent;
if(!parent.getChildren().contains(this)) {
parent.getChildren().add(this);
}
}
public void setChildren(List<ProductCategory> children) {
for(ProductCategory child : children) {
if(child.getParent() == null || !child.getParent().equals(this)) {
child.setParent(this);
}
}
this.children = children;
}
}
BaseEntity предоставляет уникальный идентификатор для других объектов. Каждый объект имеет свой собственный репозиторий:
StoreRepository:
@RepositoryRestResource(collectionResourceRel = "store", path = "store")
public interface StoreRepository extends JpaRepository<Store, Long>, JpaSpecificationExecutor<Store> {
Store findById(@Param("id") Long id);
Optional<Store> findFirstByName(@Param("name") String name);
}
ProductCategoryRepository:
@RepositoryRestResource(collectionResourceRel = "productCategory", path = "productCategory", exported = false)
public interface ProductCategoryRepository extends JpaRepository<ProductCategory, Long>, JpaSpecificationExecutor<ProductCategory> {
ProductCategory findById(@Param("id") Long id);
List<ProductCategory> findByName(@Param("name") String name);
}
Результат, который я хотел бы получить как JSON после запроса определенного магазина, будет выглядеть так:
{
id: 0,
name: 'Pet Store',
categories: [
{
id: 0,
name: 'Mammal',
children: [
{
id: 2,
name: 'Dog',
children: [
{
id: 4,
name: 'Shiba',
children: []
},
{
id: 5,
name: 'Husky',
children: []
}
]
},
{
id: 3,
name: 'Cat',
children: []
}
]
},
{
id: 1,
name: 'Bird',
children: []
}
]
}
Отношения HATEOS были удалены для краткости. В то время как объекты создаются и считываются из базы данных просто отлично, при попытке извлечь хранилище с помощью запроса GET для ресурса REST возникает следующая ошибка:
restGetStoreProductsEmbedded (com.synthapp.embeddedproject.com.repo.StoreRepositoryTest): обработка запроса не удалась; вложенное исключение: org.springframework.http.converter.HttpMessageNotWritableException: не удалось записать содержимое: бесконечная рекурсия (StackruError) (через цепочку ссылок: org.springframework.hateoas.PagedResources["_embedded"]); вложенным исключением является com.fasterxml.jackson.databind.JsonMappingException: бесконечная рекурсия (StackruError) (через цепочку ссылок: org.springframework.hateoas.PagedResources ["_ embedded"])
Я попытался решить эту проблему с помощью следующих методов:
- Не экспортировать репозиторий ProductCategory
- Удаление ProductCategoryRespository полностью для автоматической сериализации вместо использования exported = false. На самом деле не требуется, чтобы я мог выдавать запросы на сохранение базы данных в этот репозиторий, но было бы неплохо,
- Удаление двунаправленных отношений. Ссылка GitHub I в конце этой публикации включает этот пример.
- Использование @JsonManagedReference и @JsonBackReference вместо @JsonIgnore и с @JsonIgnore
- Проекция в хранилище Store, которая включает отношения категорий. Это не приводит к ошибке, но также не обеспечивает всю иерархию
- Удаление "exported = false" из репозитория ProductCategory и применение проекции InlineChildren к репозиторию ProductCategory. GET для конечной точки api/productCategory? Projection = inlineChildren вызывает ошибку о множественных связях с одинаковыми отношениями "children".
- Проекция InlineChildren на ProductCategory, как указано выше, но без двунаправленных отношений. Это не ошибка, но она также производит только первые два уровня иерархии ProductCategory.
Проект, реализующий этот пример с базой данных в памяти, можно найти на моем GitHub
1 ответ
Вы можете создать прогноз для ProductCategory
и оставить Store
из.
http://docs.spring.io/spring-data/rest/docs/current/reference/html/
Не забудьте также прочитать часть выдержки, это может быть очень полезно.
Я создаю контроллеры, которые используют PersistenEntityResourceAssembler
(поскольку вы не хотите, чтобы ваши сущности отображались, но вы хотите возвращать ресурсы), и это автоматически применяет проекцию к ресурсам без необходимости передавать их в URL-адресе - но только если это определено с помощью выдержки. в хранилище.