I have an Entity as follows:
@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;
}
}
AND:
@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;
}
}
When returning a Collection<Category>
using JAX-RS and deploying on JBoss Wildfly 8.2.0-Final, I get the following stacktrace:
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"]->
There are answers answered in questions like this question, that requires to use Jackson specific annotations. My project's requirement is to strictly stick to Java EE specific frameworks. Is there a solution that we can use to prevent infinite recursion without using Jackson annotations? If not, can we create a config file (XML file, etc.), that Jackson can use in place of annotations? The reason for this is that the application must not only be bound to Wildfly specific libraries.
I'd say you have few options here:
transient
keyword or @XmlTransient
to let JAX-RS ignore some properties / fields (they won't be marshalled),Category
instead of the whole object.There are also some Jackson specific solutions beside the @JsonIgnore
like:
@JsonView
can be used to achieve the same way in even more flexible way (e.g. it allows you to define when you want to return simplified object with no dependencies (just the ID of the related object) and when to return whole object; you specify what view to use e.g. on the JAX-RS entrypoint,I'm sure that there are also other solutions, but taking about the above ones, I would personally go with the DTO in a long-run. You can use some automatic mapping solutions like Dozer to help you with this nasty repetition job.
That being said, it's good to keep the data you present to and accept from the user separate from your internals.
Yes. Create a dedicated data structure (e.g. a Data Transfer Object or DTO) and map the fields you want to send from your HTTP endpoint.
You're mixing concerns and this usually ends up badly.
JPA entities are your API towards your data structure, REST representations (JSON or XML DTOs) are the data payload provided by your REST API.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With