- Part 1 - The Database
- Part 2 - Mapping the Database to JPA Entities
- Part 3 - Mapping JPA entities to XML (using JAXB)
- Part 4 - The RESTful Service
- Part 5 - The Client
Some developers will maintain separate JPA and JAXB models, and perform a copy step to move data between them. This can be quite a painful and error prone process. Luckily MOXy has a number of extensions that make this unnecessary:
jaxb.properties
To use the MOXy implementation of JAXB you need to add a file called jaxb.properties in with your model classes with the following entry:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Customer Entity
JAX-RS requires that we annotate the root object with the JAXB annotation @XmlRootElement. Since Customer will be our root object we will add the annotation.
package org.example; import java.io.Serializable; import javax.persistence.*; import javax.xml.bind.annotation.XmlRootElement; import java.util.Set; @Entity @NamedQuery(name = "findCustomersByCity", query = "SELECT c " + "FROM Customer c " + "WHERE c.address.city = :city") @XmlRootElement public class Customer implements Serializable { private static final long serialVersionUID = 1L; @Id private long id; @Column(name="FIRST_NAME") private String firstName; @Column(name="LAST_NAME") private String lastName; @OneToOne(mappedBy="customer", cascade={CascadeType.ALL}) private Address address; @OneToMany(mappedBy="customer", cascade={CascadeType.ALL}) private Set<PhoneNumber> phoneNumbers; public long getId() { return this.id; } public void setId(long id) { this.id = id; } public String getFirstName() { return this.firstName; } public void setFirstName(String firstName) { this.firstName = firstName; } public String getLastName() { return this.lastName; } public void setLastName(String lastName) { this.lastName = lastName; } public Address getAddress() { return this.address; } public void setAddress(Address address) { this.address = address; } public Set<PhoneNumber> getPhoneNumbers() { return this.phoneNumbers; } public void setPhoneNumbers(Set<PhoneNumber> phoneNumbers) { this.phoneNumbers = phoneNumbers; } }
Address Entity
The Address entity has a property "customer" that references back to the owning Customer entity. In other words there is a bidirectional relationship. We will use MOXy's @XmlInverseReference annotation to map this relationship. This issue is discussed deeper in the following post JPA Entities to XML - Bidirectional Relationships.
Since relationships in JPA may be "lazy" (not triggered until the property is accessed through the "get" method), we will annotate the property (get/set method), instead of the field (instance variable).
package org.example; import java.io.Serializable; import javax.persistence.*; import org.eclipse.persistence.oxm.annotations.XmlInverseReference; @Entity public class Address implements Serializable { private static final long serialVersionUID = 1L; @Id private long id; private String city; private String street; @OneToOne @PrimaryKeyJoinColumn private Customer customer; public long getId() { return this.id; } public void setId(long id) { this.id = id; } public String getCity() { return this.city; } public void setCity(String city) { this.city = city; } public String getStreet() { return this.street; } public void setStreet(String street) { this.street = street; } @XmlInverseReference(mappedBy="address") public Customer getCustomer() { return customer; } public void setCustomer(Customer customer) { this.customer = customer; } }
PhoneNumber Entity
We will also need to address the bidirectional relationship in the PhoneNumber entity.
package org.example; import java.io.Serializable; import javax.persistence.*; import org.eclipse.persistence.oxm.annotations.XmlInverseReference; @Entity @Table(name="PHONE_NUMBER") public class PhoneNumber implements Serializable { private static final long serialVersionUID = 1L; @Id private long id; private String num; private String type; @ManyToOne @JoinColumn(name="ID_CUSTOMER") private Customer customer; public long getId() { return this.id; } public void setId(long id) { this.id = id; } public String getNum() { return this.num; } public void setNum(String num) { this.num = num; } public String getType() { return this.type; } public void setType(String type) { this.type = type; } @XmlInverseReference(mappedBy="phoneNumbers") public Customer getCustomer() { return this.customer; } public void setCustomer(Customer customer) { this.customer = customer; } }
Server Setup
If your are using GlassFish 3.1.2 or later then the MOXy bundle is already included. If you are using a previous version of GlassFish then you will need to download a MOXy bundle (org.eclipse.persistence.moxy.jar) that matches the version of the EclipseLink bundles shipped with GlassFish. The necessary binaries can be downloaded from:
Next Steps
In the next post we will examine how to use the Java API for RESTful Web Services (JAX-RS) to create a RESTful service from an EJB session bean that can perform CRUD operations on our Customer model via XML.
- Part 1 - The Database
- Part 2 - Mapping the Database to JPA Entities
- Part 3 - Mapping JPA entities to XML (using JAXB)
- Part 4 - The RESTful Service
- Part 5 - The Client
Further Reading
If you enjoyed this post you may also be interested in:
- RESTful Services
- MOXy's XML Metadata in a JAX-RS Service
- MOXy as Your JAX-RS JSON Provider - Server Side
- MOXy as Your JAX-RS JSON Provider - Client Side
- Application Server Integration
I am getting com.sun.istack.SAXException2: A cycle is detected in the object graph. This will cause infinitely deep XML: myPackage.Customer@18bc6a4 -> myPackage.PhoneNumber@19e62cf -> myPackage.Customer@18bc6a4 with @XmlRootElement and @XmlInverseReference but 3.5.1 from http://jaxb.java.net/guide/Mapping_cyclic_references_to_XML.html works for me. I am using Glassfish 3.1.1. But I am having a rather strange problem with @PUT as I do get the application working correctly but I don't get the @JoinColumn(name="ID_CUSTOMER") correctly joined as it always shows NULL when I look at it using Workbench for MySQL 5.5. So if I can't get the changes (adding a new phone number) made to Customer object updated correctly with calling merge method. The application works alright but the database doesn't get updated properly, i.e. once I restart my application server the application doesn't work correctly either. The detached Customer object is not able to get managed by EntityManager with calling merge method. I am not using Address domain object in my application for the sake of simplicity. I tried only the @OneToMany relationship.
ReplyDeleteI believe your issue is due to EclipseLink JAXB (MOXy) not being configured properly in your environment. When MOXy is not configured property GlassFish defaults back to the JAXB RI.
ReplyDeleteMy blog posted the incorrect name of the MOXy bundle (it's fixed now). The MOXy bundle starts with "org.eclipse.persistence.moxy". This bundle needs to be the same version as the other eclipselink bundles that ship with GlassFish. In GlassFish 3.1.1 this is EclipseLink 2.3.0.
This type of issue is hard to handle in the comments section. Feel free to start an email discussion with me via my Contact Me page.
-Blaise
Its working absolutely fine now. Thanks a heap. Really good article. Thanks for sharing.
ReplyDeleteExcelente tutorial. Me salvo de un buen problema.
ReplyDeleteWhere exactly to I put my jaxb.properties its a little obscure can you please provide an example of where the "model classes" would be. ta.
ReplyDeleteIn part 4 of this series I have a fully expanded view of what the WAR file contains including the location of the java.properties file.
Delete- Creating a RESTful Web Service - Part 4/5
I have been very useful the info you posted, thank you very much
ReplyDelete