2015-06-06 2 views
4

У меня есть Entity следующим образом:Предотвращение зацикливание в JAX-RS на JPA Entity сериализации (JSON) (без использования Jackson аннотаций)

@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, требуют использования специальных аннотаций Джексона. Требование моего проекта - строго придерживаться специфических фреймворков Java EE. Есть ли решение, которое мы можем использовать для предотвращения бесконечной рекурсии без использования аннотаций Джексона? Если нет, можем ли мы создать файл конфигурации (файл XML и т. Д.), Который Джексон может использовать вместо аннотаций? Причиной этого является то, что приложение должно не только привязываться к конкретным библиотекам Wildfly.

+0

Так что во всем проекте вы вообще не используете _any_ сторонние библиотеки, просто javaee-api? –

+0

@peeskillet да. Я считаю, что это возможно, и этот проект будет доказательством этого. –

ответ

2

Да. Создайте выделенную структуру данных (например, объект передачи данных или DTO) и сопоставьте поля, которые вы хотите отправить с конечной точки HTTP.

Вы смешиваете проблемы, и это обычно заканчивается плохо.

Объекты JPA - это ваш API к вашей структуре данных, представления REST (JSON или XML DTO) - это полезная нагрузка данных, предоставляемая вашим REST API.

+0

Я ничего не смешиваю. Мой RESTful WS возвращает «Collection», а JBoss использует «RESTEasy» (реализация JAX-RS), которая использует Jackson для преобразования моего объекта в строку JSON. Это делается на уровне контейнера, и я не контролирую его. –

+0

@BuhakeSindi вы можете не понимать ответ. В основном, ответ гласит: «Использовать DTOs» _, с которым я обычно должен согласиться. –

+0

DTO кажется идеальным решением этой проблемы. –

2

Я бы сказал, что у вас есть несколько вариантов здесь:

  1. Используйте transient ключевое слово или @XmlTransient позволить JAX-RS игнорировать некоторые свойства/поля (они не будут ранжированы)
  2. Использование DTO для лучше отразить структуру данных для конечного пользователя; во времени различия между вашей сущностью, как она смоделирована в РСУБД и что вы возвращаете пользователю, будет больше и больше,
  3. Используйте комбинацию из двух вышеуказанных параметров и отметьте некоторые поля как переходные и в то же время предоставить другой «Аксессуар JAX-RS», например возвращает только идентификатор Category вместо всего объекта.

Есть также некоторые конкретные решения Джексон рядом с @JsonIgnore как:

  • Jackson views - @JsonView могут быть использованы для достижения такого же, как в еще более гибким способом (например, это позволяет определить, когда вы хотите вернуть упрощенный объект без зависимостей (только идентификатор связанного объекта) и когда вернуть весь объект, вы указываете, какой вид использовать, например, на входной точке JAX-RS,
  • Object Identity, который будет распознавать круговые зависимости при сортировке объекта и предотвратить бесконечное r ecursion (первый удар объекта означает, что он помещается как целое как есть, каждый другой удар того же объекта означает поместить только его идентификатор).

Я уверен, что есть и другие решения, но с учетом вышеперечисленных я бы лично пошел с DTO в долгосрочной перспективе. Вы можете использовать некоторые автоматические картографические решения, такие как Dozer, чтобы помочь вам в этом неприятном повторении.
Это, как говорится, полезно хранить данные, которые вы представляете, и принимать от пользователя отдельно от ваших внутренних компонентов.

Смежные вопросы