A cycle is detected in the object graph. This will cause infinitely deep XML
Solution 1
Your object graph is cyclic. There is nothing intrinsically wrong with that, and it is a natural consequence of using JPA.
Your problem is not that your object graph is cyclic, but that you are encoding it in a format which cannot handle cycles. This isn't a Hibernate question, it's a JAXB question.
My suggestion would be to stop JAXB from attempting to marshal the application
property of the EnvironmentDTO
class. Without that property the cyclic graph becomes a tree. You can do this by annotating that property with @XmlTransient
.
(confession: i learned about this annotation by reading a blog post by Mr Doughan, which i came across after reading his answer to this question!)
Solution 2
Note: I'm the EclipseLink JAXB (MOXy) lead and a member of the JAXB (JSR-222) expert group.
MOXy offers the @XmlInverseReference
extension to handle this use case. Below is an example of how to apply this mapping on two entities with a bidirectional relationship.
Customer
import javax.persistence.*;
@Entity
public class Customer {
@Id
private long id;
@OneToOne(mappedBy="customer", cascade={CascadeType.ALL})
private Address address;
}
Address
import javax.persistence.*;
import org.eclipse.persistence.oxm.annotations.*;
@Entity
public class Address implements Serializable {
@Id
private long id;
@OneToOne
@JoinColumn(name="ID")
@MapsId
@XmlInverseReference(mappedBy="address")
private Customer customer;
}
For More Information
- http://blog.bdoughan.com/2010/07/jpa-entities-to-xml-bidirectional.html
- http://blog.bdoughan.com/2013/03/moxys-xmlinversereference-is-now-truly.html
Solution 3
My advice is not exposing your JPA entity class to your webservices. You can create different POJO class and convert your JPA entity to the POJO. For example:
this is your JPA entity
import javax.persistence.*;
@Entity
public class Customer {
@Id
private long id;
@OneToOne(mappedBy="customer", cascade={CascadeType.ALL})
private Address address;
}
you should use this class for your webservices:
public class CustomerModel{
private long id;
//you can call different WS to get the Address class, or combine to this model
public void setFromJpa(Customer customer){
this.id = customer.id;
}
}
Prats
Updated on June 05, 2022Comments
-
Prats about 2 years
I have two DTO objects say A and B which are having getters and setters and are used to take data from the database. The problem is when I am calling A, B gets called and B again points itself to A and a cycle is created.
I cannot ignore/hide the method which is creating the cycle. I need to take the whole data of A and B.
Is there any way to achieve it ?
Please help
This is my code which is causing the problem. This is application DTO which is calling environment DTO
@OneToMany(mappedBy="application", fetch=FetchType.LAZY ,cascade=CascadeType.ALL ) public Set<EnvironmentDTO> getEnvironment() { return environment; } public void setEnvironment(Set<EnvironmentDTO> environment) { this.environment = environment; }
And this is environment DTO which is calling the application DTO
@ManyToOne(targetEntity=ApplicationDTO.class ) @JoinColumn(name="fk_application_Id") public ApplicationDTO getApplication() { return application; } public void setApplication(ApplicationDTO application) { this.application = application; }
Here cycle is getting created
This is my rest call which will give result in XML format and I think while creating XML cycle is getting created
@GET @Path("/get") @Produces({MediaType.APPLICATION_XML}) public List<ApplicationDTO> getAllApplications(){ List<ApplicationDTO> allApplication = applicationService.getAllApplication(); return allApplication; }
This is the Application DTO class
@Entity @Table(name="application") @org.hibernate.annotations.GenericGenerator( name ="test-increment-strategy",strategy = "increment") @XmlRootElement public class ApplicationDTO implements Serializable { @XmlAttribute public Long appTypeId; private static final long serialVersionUID = -8027722210927935073L; private Long applicationId; private String applicationName; private ApplicationTypeDTO applicationType; private String applicationDescription; private Integer owner; private Integer createdBy; private Integer assignedTo; private Date createTime; private Date modifiedTime; private Set<EnvironmentDTO> environment; @Id @GeneratedValue(generator = "test-increment-strategy") @Column(name = "applicationId") public Long getApplicationId() { return applicationId; } private void setApplicationId(Long applicationId) { this.applicationId = applicationId; } @Column(name = "applicationName") public String getApplicationName() { return applicationName; } public void setApplicationName(String applicationName) { this.applicationName = applicationName; } @ManyToOne(targetEntity=ApplicationTypeDTO.class ,fetch = FetchType.LAZY ) @JoinColumn(name="applicationType") public ApplicationTypeDTO getApplicationType() { return applicationType; } public void setApplicationType(ApplicationTypeDTO applicationType) { this.applicationType = applicationType; } @Column(name = "description") public String getApplicationDescription() { return applicationDescription; } public void setApplicationDescription(String applicationDescription) { this.applicationDescription = applicationDescription; } @Column(name = "owner") public Integer getOwner() { return owner; } public void setOwner(Integer owner) { this.owner = owner; } @Column(name = "createdBy") public Integer getCreatedBy() { return createdBy; } public void setCreatedBy(Integer createdBy) { this.createdBy = createdBy; } @Column(name = "assignedTo") public Integer getAssignedTo() { return assignedTo; } public void setAssignedTo(Integer assignedTo) { this.assignedTo = assignedTo; } @Column(name = "createTime") public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } @Column(name = "modifiedTime") public Date getModifiedTime() { return modifiedTime; } public void setModifiedTime(Date modifiedTime) { this.modifiedTime = modifiedTime; } @OneToMany(mappedBy="application", fetch=FetchType.LAZY ,cascade=CascadeType.ALL ) public Set<EnvironmentDTO> getEnvironment() { return environment; } public void setEnvironment(Set<EnvironmentDTO> environment) { this.environment = environment; }
This is the Environment DTO class
@Entity @Table(name="environment") @org.hibernate.annotations.GenericGenerator( name = "test-increment-strategy", strategy = "increment") @XmlRootElement public class EnvironmentDTO implements Serializable { @XmlAttribute public Long envTypeId; @XmlAttribute public Long appId; private static final long serialVersionUID = -2756426996796369998L; private Long environmentId; private String environmentName; private EnvironmentTypeDTO environmentType; private Integer owner; private Date createTime; private Set<InstanceDTO> instances; private ApplicationDTO application; @Id @GeneratedValue(generator = "test-increment-strategy") @Column(name = "envId") public Long getEnvironmentId() { return environmentId; } private void setEnvironmentId(Long environmentId) { this.environmentId = environmentId; } @Column(name = "envName") public String getEnvironmentName() { return environmentName; } public void setEnvironmentName(String environmentName) { this.environmentName = environmentName; } @ManyToOne(targetEntity=EnvironmentTypeDTO.class) @JoinColumn(name = "envType") public EnvironmentTypeDTO getEnvironmentType() { return environmentType; } public void setEnvironmentType(EnvironmentTypeDTO environmentType) { this.environmentType = environmentType; } @Column(name = "owner") public Integer getOwner() { return owner; } public void setOwner(Integer owner) { this.owner = owner; } @Temporal(TemporalType.DATE) @Column(name = "createTime") public Date getCreateTime() { return createTime; } public void setCreateTime(Date createTime) { this.createTime = createTime; } @OneToMany(mappedBy="environment", cascade=CascadeType.ALL, fetch = FetchType.EAGER) public Set<InstanceDTO> getInstances() { return instances; } public void setInstances(Set<InstanceDTO> instances) { this.instances = instances; } @ManyToOne(targetEntity=ApplicationDTO.class ) @JoinColumn(name="fk_application_Id") //@XmlTransient public ApplicationDTO getApplication() { return application; } public void setApplication(ApplicationDTO application) { this.application = application; }