java - 防止 JAX-RS 中 JPA 实体序列化 (JSON) 的无限递归(不使用 Jackson 注释)

标签 java jpa jackson jax-rs resteasy

我有一个实体如下:

@XmlRootElement
@Entity
@Table(name="CATEGORY")
@Access(AccessType.FIELD)
@Cacheable
@NamedQueries({
    @NamedQuery(name="category.countAllDeleted", query="SELECT COUNT(c) FROM Category c WHERE c.deletionTimestamp IS NOT NULL"),
    @NamedQuery(name="category.findAllNonDeleted", query="SELECT c from Category c WHERE c.deletionTimestamp IS NULL"),
    @NamedQuery(name="category.findByCategoryName", query="SELECT c FROM Category c JOIN c.descriptions cd WHERE LOWER(TRIM(cd.name)) LIKE ?1")
})
public class Category extends AbstractSoftDeleteAuditableEntity<Integer> implements za.co.sindi.persistence.entity.Entity<Integer>, Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 4600301568861226295L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="CATEGORY_ID", nullable=false)
    private int id;

    @ManyToOne
    @JoinColumn(name="PARENT_CATEGORY_ID")
    private Category parent;

    @OneToMany(cascade= CascadeType.ALL, mappedBy="category")
    private List<CategoryDescription> descriptions;

    public void addDescription(CategoryDescription description) {
        if (description != null) {
            if (descriptions == null) {
                descriptions = new ArrayList<CategoryDescription>();
            }

            descriptions.add(description);
        }
    }

    /* (non-Javadoc)
     * @see za.co.sindi.entity.IDBasedEntity#getId()
     */
    public Integer getId() {
        // TODO Auto-generated method stub
        return id;
    }

    /* (non-Javadoc)
     * @see za.co.sindi.entity.IDBasedEntity#setId(java.io.Serializable)
     */
    public void setId(Integer id) {
        // TODO Auto-generated method stub
        this.id = (id == null) ? 0 : id;
    }

    /**
     * @return the parent
     */
    public Category getParent() {
        return parent;
    }

    /**
     * @param parent the parent to set
     */
    public void setParent(Category parent) {
        this.parent = parent;
    }

    /**
     * @return the descriptions
     */
    public List<CategoryDescription> getDescriptions() {
        return descriptions;
    }

    /**
     * @param descriptions the descriptions to set
     */
    public void setDescriptions(List<CategoryDescription> descriptions) {
        this.descriptions = descriptions;
    }
}

和:

@XmlRootElement
@Entity
@Table(name="CATEGORY_DESCRIPTION")
@Access(AccessType.FIELD)
@Cacheable
public class CategoryDescription extends AbstractModifiableAuditableEntity<CategoryDescriptionKey> implements za.co.sindi.persistence.entity.Entity<CategoryDescriptionKey>, Serializable {

    /**
     * 
     */
    private static final long serialVersionUID = 4506134647012663247L;

    @EmbeddedId
    private CategoryDescriptionKey id;

    @MapsId("categoryId")
    @ManyToOne/*(fetch=FetchType.LAZY)*/
    @JoinColumn(name="CATEGORY_ID", insertable=false, updatable=false, nullable=false)
    private Category category;

    @MapsId("languageCode")
    @ManyToOne/*(fetch=FetchType.LAZY)*/
    @JoinColumn(name="LANGUAGE_CODE", insertable=false, updatable=false, nullable=false)
    private Language language;

    @Column(name="CATEGORY_NAME", nullable=false)
    private String name;

    @Column(name="DESCRIPTION_PLAINTEXT", nullable=false)
    private String descriptionPlainText;

    @Column(name="DESCRIPTION_MARKDOWN", nullable=false)
    private String descriptionMarkdown;

    @Column(name="DESCRIPTION_HTML", nullable=false)
    private String descriptionHtml;

    /* (non-Javadoc)
     * @see za.co.sindi.entity.IDBasedEntity#getId()
     */
    public CategoryDescriptionKey getId() {
        // TODO Auto-generated method stub
        return id;
    }

    /* (non-Javadoc)
     * @see za.co.sindi.entity.IDBasedEntity#setId(java.io.Serializable)
     */
    public void setId(CategoryDescriptionKey id) {
        // TODO Auto-generated method stub
        this.id = id;
    }

    /**
     * @return the category
     */
    public Category getCategory() {
        return category;
    }

    /**
     * @param category the category to set
     */
    public void setCategory(Category category) {
        this.category = category;
    }

    /**
     * @return the language
     */
    public Language getLanguage() {
        return language;
    }

    /**
     * @param language the language to set
     */
    public void setLanguage(Language language) {
        this.language = language;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the descriptionPlainText
     */
    public String getDescriptionPlainText() {
        return descriptionPlainText;
    }

    /**
     * @param descriptionPlainText the descriptionPlainText to set
     */
    public void setDescriptionPlainText(String descriptionPlainText) {
        this.descriptionPlainText = descriptionPlainText;
    }

    /**
     * @return the descriptionMarkdown
     */
    public String getDescriptionMarkdown() {
        return descriptionMarkdown;
    }

    /**
     * @param descriptionMarkdown the descriptionMarkdown to set
     */
    public void setDescriptionMarkdown(String descriptionMarkdown) {
        this.descriptionMarkdown = descriptionMarkdown;
    }

    /**
     * @return the descriptionHtml
     */
    public String getDescriptionHtml() {
        return descriptionHtml;
    }

    /**
     * @param descriptionHtml the descriptionHtml to set
     */
    public void setDescriptionHtml(String descriptionHtml) {
        this.descriptionHtml = descriptionHtml;
    }   
}

返回 Collection<Category> 时使用 JAX-RS 并在 JBoss Wildfly 8.2.0-Final 上部署,我得到以下堆栈跟踪:

Caused by: com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError) (through reference chain: za.co.sindi.unsteve.persistence.entity.Category["descriptions"]->org.hibernate.collection.internal.PersistentBag[0]->za.co.sindi.unsteve.persistence.entity.CategoryDescription["category"]->za.co.sindi.unsteve.persistence.entity.Category["descriptions"]->

在类似 this question 的问题中有答案。 ,这需要使用 Jackson 特定的注释。我的项目要求是严格坚持使用 Java EE 特定框架。有没有一种解决方案可以在不使用 Jackson 注释的情况下防止无限递归?如果没有,我们可以创建一个 Jackson 可以用来代替注释的配置文件(XML 文件等)吗?这样做的原因是应用程序不能只绑定(bind)到 Wildfly 特定库。

最佳答案

我会说你在这里有几个选择:

  1. 使用transient 关键字或@XmlTransient让 JAX-RS 忽略一些属性/字段(它们不会被编码),
  2. 使用 DTO 更好地为最终用户反射(reflect)您的数据结构;随着时间的推移,您的实体、它在 RDBMS 中的建模方式以及您返回给用户的内容之间的差异将越来越大,
  3. 结合使用上述两个选项并将某些字段标记为 transient ,同时提供其他“JAX-RS 友好”访问器,例如仅返回 Category 的 ID 而不是整个对象。

除了 @JsonIgnore 之外还有一些 Jackson 特定的解决方案,例如:

  • Jackson views -- @JsonView 可用于以更灵活的方式实现相同的方式(例如,它允许您定义何时要返回没有依赖项的简化对象(只是相关对象的 ID)以及何时返回整个对象;您指定要使用的 View ,例如在 JAX-RS 入口点上,
  • Object Identity这将在编码对象时识别循环依赖并防止无限递归(对象的第一次命中意味着将其作为一个整体按原样放置,同一对象的每一次其他命中意味着仅放置其 ID)。

我确信还有其他解决方案,但考虑到上述解决方案,从长远来看,我个人会选择 DTO。您可以使用一些自动映射解决方案,例如 Dozer来帮助你完成这个讨厌的重复工作。
话虽这么说,但最好将您提供给用户并从用户那里接受的数据与您的内部数据分开。

关于java - 防止 JAX-RS 中 JPA 实体序列化 (JSON) 的无限递归(不使用 Jackson 注释),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/30683768/

相关文章:

java - 根据属性值 spring boot 从列表中过滤对象

java - 我应该将 json 字符串解析为 json 对象还是直接操作字符串

java - 如何将字符串 Arraylist 从一个 Activity 传递到另一个 Activity ?

java - 选择和悬停覆盖 SWT 表格组件中的单元格背景颜色

Java MongoDB 假装是复制从属

java - Hibernate Eclipse 未知实体(使用 javax.persistence.Entity)

java - 修改集合会使实体变脏

java - 接口(interface)向下转型后,模拟对象的方法返回 null

java - 如何在现有 Spring Hibernate 应用程序中包含 Spring JPA

java - 如何根据 URL 路径和嵌套级别配置 Jackson 和 Spring 以不同方式呈现对象