A cycle is detected in the object graph. This will cause infinitely deep XML

15,197

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

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;
    }
}
Share:
15,197
Prats
Author by

Prats

Updated on June 05, 2022

Comments

  • Prats
    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;
    }