July 26, 2010

JPA Entities to XML - Bidirectional Relationships

JAXB is your best choice when binding Java objects to XML.  But what happens when those Java objects turn out to be JPA entities?  For the most part it is business as usual, although there are a couple things to be aware of.  In this post we'll cover a MOXy extension that can be used to map a bidirectional relationship.


We'll use the following model.  Notice how Customer has a relationship to Address, and Address has a relationship back to Customer.  In JPA one direction of the relationship is mapped (in this case the customer property on Address), and the other direction specifies a mapping to leverage.

import javax.persistence.*;

@Entity
public class Customer {

    @Id
    private long id;

    @OneToOne(mappedBy="customer", cascade={CascadeType.ALL})
    private Address address;

}

import javax.persistence.*;

@Entity
public class Address implements Serializable {

    @Id
    private long id;

    @MapsId
    @OneToOne
    @JoinColumn(name="ID")
    private Customer customer;

}

Using vanilla JAXB to marshal these objects to XML you need to mark one direction @XmlTrasient, this prevents JAXB from entering an infinite loop during marshalling (object-to-XML).  During unmarshalling (XML-to-object) however you are responsible for populating the back pointer yourself.

import javax.persistence.*;
import javax.xml.bind.annotation.*;

@Entity
public class Address implements Serializable {

    @Id
    private long id;

    @OneToOne
    @JoinColumn(name="ID")
    @MapsId
    @XmlTransient
    private Customer customer;

}

MOXy offers an extension that will populate the back pointer for you, this is done with the @XmlInverseReference annotation.  Note how @XmlInverseReference annotation leverages the same "mappedBy" concept.

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;

}

Now as I'll demonstrate below you can move your data from JPA to JAXB and back again without having to modify the data:

// Read customer from database
EntityManagerFactory emf = 
    Persistence.createEntityManagerFactory("CustomerService");
EntityManager em = emf.createEntityManager();
Customer customer = em.find(Customer.class, (long) 1);
        
// Save customer to XML
JAXBContext jc = JAXBContext.newInstance(Customer.class);
Marshaller marshaller = jc.createMarshaller();
StringWriter writer = new StringWriter();
marshaller.marshal(customer, writer);
       
// Load customer from XML 
Unmarshaller unmarshaller = jc.createUnmarshaller();
StringReader reader = new StringReader(writer.toString());
Customer detachedCustomer = 
    (Customer) unmarshaller.unmarshal(reader);
detachedCustomer.setLastName("Jones");

// Persist customer to database
Customer mergedCustomer = em.merge(detachedCustomer);
em.getTransaction().begin();
em.persist(mergedCustomer);
em.getTransaction().commit();
em.close();

In future posts I'll describe other MOXy extensions used to map JPA entities to XML.

14 comments:

  1. hi,, i want to ask how to show the value of XML that should be generated by @XmlInverseReference ,, because i need the value of all generated value from that variable but because using @XmlInverseReference i can't get any of xml value that i want... Any solution??

    ReplyDelete
  2. Hi sby,

    In this example when you marshal a Customer that contains an Address, both Customer and Address information will appear in the XML. @XmlInverseReference comes into play during the unmarshal when it will populate the back pointer for you.

    -Blaise

    ReplyDelete
  3. Hi, I have 2 Objects that have a bi-directional relation (A-B). Now I have 2 services:
    1: gets A with a reference to B (this works as described)
    2: gets B with a reference to A (the other side around A is null)
    Is there a solution for that problem?

    ReplyDelete
    Replies
    1. Hi Pascal,

      You could leverage MOXy's external binding document to apply a second set of mappings. Below is an example where this strategy is leveraged to map a single object model to both the Google and Yahoo weather services:
      - Mapping Objects to Multiple XML Schemas - Weather Example

      -Blaise

      Delete
    2. Thanks Blaise, that's what I was looking for. Pascal

      Delete
  4. Hi Blaise,

    I have another problem. It works if the relation is one-to-many with concrete types. But if the children resp. the parent are Abstract Classes, the parent (annotated with @XmlInverseReference) is null.
    <code>
    @Entity
    @Table(name = ProjectConstants.DBTABLE_BALANCEAREA)
    @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
    @DiscriminatorColumn(name = "DISCRIMINATOR", discriminatorType = DiscriminatorType.STRING)
    public abstract class AbstractBalanceArea extends AbstractRootPDO {

    @OneToMany(fetch = FetchType.EAGER, cascade = cascadeType.ALL, mappedBy = "parent")
    @XmlAnyElement(lax = true)
    Set children = new HashSet();
    @ManyToOne
    @JoinColumn(name = "PARENTID")
    @XmlTransient //@XmlInverseReference(mappedBy = "children")
    AbstractBalanceArea parent;
    </code>

    Did i miss something or is that not possible?
    Pascal

    ReplyDelete
    Replies
    1. Hi Pascal,

      We currently do not support the @XmlAnyElement/@XmlInverseReference combination. I have entered the following bugs you can use to track this:

      - Bug 370579 - Enhancement: support @XmlInverseReference with @XmlAnyElement
      - Bug 370573 - Enhancement: support @XmlInverseReference with @XmlElementRef

      -Blaise

      Delete
  5. if you use something like:

    @XmlType(name = "bbb")
    @XmlRootElement
    @XmlAccessorType(XmlAccessType.FIELD)

    Wont need @XMLTransient.

    ReplyDelete
    Replies
    1. When you use @XmlAccessorType(XmlAccessType.FIELD) on a JPA entity, the behaviour can vary depending upon whether or not the lazy loading for that value has been populated. I recommend explicitly mapping what you want to have happen instead of counting on a side effect that you have no control over.

      -Blaise

      Delete
  6. hi does it work in a similar way if i use it to serealize and deserealize my pojos in JSON? i got problems with bidirectional relations, its working fine with XML but not JSON

    ReplyDelete
    Replies
    1. Hi,

      Yes, you can use the @XmlInverseReference annotation with MOXy's native JSON binding:
      - JSON Binding with EclipseLink MOXy - Twitter Example

      MOXy uses the same metadata for both its XML and JSON binding:
      - Binding to JSON & XML - Geocode Example

      -Blaise

      Delete
  7. Hey my friend and I posted this question on stackoverflow, we were reading you blog and it was helping us a lot, but we didn't see clear examples and documentation that we could find explaining the problem we are having, if you don't mind taking a look.

    http://stackoverflow.com/questions/17005039/jax-b-xml-inverse-reference-with-many-to-many-relationship

    Your blog has been really helpful and you have done a wonderful job by the way. Any help is appreciated.

    ReplyDelete
    Replies
    1. I have posted an answer to your question on Stack Overflow:
      - http://stackoverflow.com/a/17031101/383861

      Here is link to another @XmlInverseReference post that may help:
      - MOXy's @XmlInverseReference is now Truly Bidirectional

      Delete
    2. Thank you for clearing things up, I really apprciate it. I think/hope we set most of the annotations correctly, I'll be taking a look after work, but this definetly helps make sense of what we weren't sure of. Thank you again.
      -Kevin

      Delete