Ресурс репозитория не может встраивать объект, ссылающийся на себя

У меня есть две сущности, 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-адресе - но только если это определено с помощью выдержки. в хранилище.

Другие вопросы по тегам