11

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;
}
Prats
  • 1,515
  • 6
  • 16
  • 30

3 Answers3

11

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!)

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • I have added my DTO classes, please have a look – Prats Jun 25 '13 at 12:24
  • I tried @XmlTransient, but using this hides the environmentDTO contents and I only get applicationDTO XML. But, I need both applicationDTO and environmentDTO in one XML – Prats Jun 26 '13 at 10:07
  • Could you please suggest a way how can I achieve both the DTO's in one XML – Prats Jun 26 '13 at 10:17
  • 1
    I suggested you annotate the `application` property of the `EnvironmentDTO` class with `@XmlTransient`. This should not hide the other properties of `EnvironmentDTO`. Did you do exactly what i suggested? – Tom Anderson Jun 26 '13 at 11:45
5

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

bdoughan
  • 147,609
  • 23
  • 300
  • 400
  • This solution gets a better result than mine (using `@XmlTransient`). However, it involves using non-standard annotations that only work with MOXy. Can we expect to see these annotations graduate into the JAXB standard, and if so, do you have any idea when? – Tom Anderson Jun 25 '13 at 17:10
  • I am very new to JAXB, I am not able to understand how to use MOXY'S with JAXB. I have gone thorugh your links but I am still not clear on how to use it. Could you please explain how to use it ? – Prats Jun 26 '13 at 10:12
  • @user2034743 - MOXy is a standard JAXB provider, you can specify it by adding a `jaxb.properties` file in the same package as your domain model (see: http://blog.bdoughan.com/2011/05/specifying-eclipselink-moxy-as-your.html). Here is a link to an example that may help: https://github.com/bdoughan/blog20110322 – bdoughan Jun 26 '13 at 15:08
  • I have added jaxb.properties file but my eclipse is not able to recognise @XmlInverseReference element. Do I have to add some jars for using MOXy or add something into my web.xml to pull in MOXy into my project ?? – Prats Jun 28 '13 at 06:29
  • I have also downloaded EclipseLink 2.5.0 nightly, but what to do after downloading it. You have not specified it anywhere, how to use it ? – Prats Jun 28 '13 at 07:18
  • Could you please suggest me a way to implement this ? – Prats Jun 28 '13 at 12:10
  • I could help you out using Stack Overflow chat. Would that work for you? – bdoughan Jun 28 '13 at 13:05
  • Sure, could you tell me what time you are free ? – Prats Jul 01 '13 at 05:29
  • @Prats - It is probably easiest to contact me using the following link: http://blog.bdoughan.com/p/contact_01.html. I'm in the eastern time zone so I'm not sure how that overlaps with where you are. – bdoughan Jul 02 '13 at 18:21
1

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;
    }
}
smftr
  • 923
  • 3
  • 17
  • 31