存储库休息资源无法嵌入自引用对象

时间:2016-03-07 14:48:26

标签: spring rest spring-data spring-data-rest spring-hateoas

我有两个实体,一个 Store 和一个 ProductCategory ,我坚持并希望从 JpaRepository 中使用 @RepositoryRestResource 。商店有一系列产品类别,产品类别与自己有父/子关系。此外,我还希望在向Store的REST存储库发出GET请求时嵌入产品类别层次结构。

实体看起来如下:

商店:

@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关系。虽然可以很好地创建和从数据库中读取对象,但是当尝试通过针对REST资源的GET请求检索存储时,会产生以下错误:

  

restGetStoreProductsEmbedded(com.synthapp.embeddedproject.com.repo.StoreRepositoryTest):请求处理失败;嵌套异常是org.springframework.http.converter.HttpMessageNotWritableException:无法写入内容:无限递归(StackOverflowError)(通过引用链:org.springframework.hateoas.PagedResources [&#34; _embedded&#34;]);嵌套异常是com.fasterxml.jackson.databind.JsonMappingException:无限递归(StackOverflowError)(通过引用链:org.springframework.hateoas.PagedResources [&#34; _embedded&#34;])

我试图通过以下方法解决这个问题:

  • 不导出ProductCategory repo
  • 完全删除 ProductCategoryRespository 以进行自动序列化,而不是使用 exported = false 。我真的不需要能够向这个存储库发出数据库保存请求,但是很好,
  • 删除双向关系。本帖子末尾的GitHub I引用包括这个例子。
  • 使用 @JsonManagedReference @JsonBackReference 代替 @JsonIgnore ,并使用 @JsonIgnore
  • 商店资源库中包含类别的投影 关系。这不会产生错误,但也不会产生错误 提供了整个层次结构
  • 删除&#34; exported = false&#34;从ProductCategory repo并将InlineChildren投影应用于ProductCategory存储库。针对api / productCategory的一个GET?projection = inlineChildren端点导致关于具有相同关系的多个关联链接的错误&#34; children&#34;。
  • 如上所述在ProductCategory上的InlineChildren投影,但没有双向关系。这不会出错,但它也只生成ProductCategory层次结构的前两层。

在我的GitHub

中可以找到使用内存数据库实现此示例的项目

1 个答案:

答案 0 :(得分:0)

您可以为ProductCategory创建一个投影,并将Store退出。

http://docs.spring.io/spring-data/rest/docs/current/reference/html/#projections-excerpts

请务必阅读有关摘录的部分,这可能非常有用。

我创建了使用PersistenEntityResourceAssembler的控制器(因为你不希望暴露实体但你想要返回资源),这将自动将投影应用于资源而不需要在URL中传递它 - 但只有在存储库中通过摘录定义它。