Loading
EclipseLink Solutions Guide for EclipseLink
Release 2.5
  Go To Table Of Contents
Contents
 Search
Other Searches
 PDF
PDF
Comments
Comments

Binding JPA Entities to XML

The following examples demonstrate how to bind JPA entities to XML by using JAXB annotations. For more information about binding, see Understanding XML Binding for more information about JAXB, see Understanding JAXB

Binding JPA Relationships to XML

The following exercise demonstrate show to use JAXB to derive an XML representation from a set of JPA entities, a process called "binding" (read about XML binding in Binding JPA Entities to XML). These examples will show how to bind two common JPA relationships:

  • Privately-owned relationships

  • Shared reference relationships

to map an Employee entity to that employee's phone number, address, and department.

Task 1: Define the Accessor Type and Import Classes

Since all of the following examples use the same accessor type, FIELD, define it at the package level by using the JAXB annotation @XmlAccessorType. At this point, you would also import the necessary classes:

@XmlAccessorType(XmlAccessType.FIELD)
package com.example.model;
 
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;

Task 2: Map Privately-Owned Relationships

A "privately-owned" relationship occurs when the target object is only referenced by a single source object. This type of relationship can be either one-to-one and embedded or one-to-many.

This Task shows how to create bi-directional mappings for both of these types of relationships between the Employee entity and the Address and PhoneNumber entities.

Mapping a One-to-One and Embedded Relationship

The JPA @OneToOne and @Embedded annotations indicate that only one instance of the source entity is able to refer to the same target entity instance. This example shows how to map the Employee entity to the Address entity and back. This is considered a one-to-one mapping because the employee can be associated with only one address. Since this relationship is bi-directional—that is, Employee points to Address, which must point back to Employee—it uses the EclipseLink extension @XmlInverseReference to represent the back-pointer.

To create the one-to-one and embedded mapping:

  1. Ensure that the accessor type FIELD has been defined at the package level, as described in Task 1: Define the Accessor Type and Import Classes.

  2. Map one direction of the relationship, in this case, the employee property on Address, by inserting the @OneToOne annotation in the Employee entity:

        @OneToOne(mappedBy="resident")
        private Address residence;
    

    The mappedBy argument indicates that the relationship is owned by the resident field.

  3. Map the return direction—that is, the address property on Employee—by inserting the @OneToOne and @XmlInverseMapping annotations into the Address entity:

        @OneToOne
        @JoinColumn(name="E_ID")
        @XmlInverseReference(mappedBy="residence")
        private Employee resident;
    

    The mappedBy field indicates that this relationship is owned by the residence field. @JoinColumn identifies the column that will contain the foreign key.

The entities should look like those shown in Example 15-1 and Example 15-2.

Mapping a One-to-Many Relationship

The JPA @OneToMany annotation indicates that a single instance of the source entity can refer to multiple instances of the same target entity. For example, one employee can have multiple phone numbers, such as a land line, a mobile number, a desired contact number, and an alternative workplace number. Each different number would be an instance of the PhoneNumber entity and a single Employee entity could point to each instance.

This Task maps the employee to one of that employee's phone numbers and back. Since the relationship between Employee and PhoneNumber is bi-directional, the example again uses the EclipseLink extension @XmlInverseReference to map the back-pointer.

To create a one-to-many mapping:

  1. Ensure that the accessor type FIELD has been defined at the package level, as described in Task 1: Define the Accessor Type and Import Classes.

  2. Map one direction of the relationship, in this case, the employee property on PhoneNumber, by inserting the @OneToMany annotation in the Employee entity:

        @OneToMany(mappedBy="contact")
        private List<PhoneNumber> contactNumber;
    

    The mappedBy field indicates that this relationship is owned by the contact field.

  3. Map the return direction—that is, the phone number property on Employee—by inserting the @ManyToOne and @XmlInverseMapping annotations into the PhoneNumber entity:

        @ManyToOne
        @JoinColumn(name="E_ID", referencedColumnName = "E_ID")
        @XmlInverseReference(mappedBy="contactNumber")
        private Employee contact;
    

    The mappedBy field indicates that this relationship is owned by the contactNumber field. The @JoinColumn annotation identifies the column that will contain the foreign key (name="E_ID") and the column referenced by the foreign key (referencedColumnName = "E_ID").

The entities should look like those shown in Example 15-1 and Example 15-3.

Task 3: Map the Shared Reference Relationship

A shared reference relationship occurs when target objects are referenced by multiple source objects. For example, a business might be segregated into multiple departments, such as IT, human resources, finance, and so on. Each of these departments has multiple employees of differing job descriptions, pay grades, locations, and so on. Managing departments and employees requires shared reference relationships.

Since a shared reference relationship cannot be safely represented as nesting in XML, we use key relationships. In order to leverage the ID fields on JPA entities, you need to use the EclipseLink JAXB @XmlID annotation on non-String fields and properties and @XmlIDREF on string fields and properties.

This section contains examples that show how to map a many-to-one shared reference relationship and a many-to-many shared reference relationship.

Mapping a Many-to-One Shared Reference Relationship

In a many-to-one mapping, one or more instances of the source entity are able to refer to the same target entity instance. This example demonstrates how to map an employee to one of that employee's multiple phone numbers.

To map a many-to-one shared reference relationship:

  1. Ensure that the accessor type FIELD has been defined at the package level, as described in Task 1: Define the Accessor Type and Import Classes.

  2. Map one direction of the relationship, in this case the phone number property on Employee, by inserting the @ManyToOne annotation in the PhoneNumber entity:

        @ManyToOne
        @JoinColumn(name="E_ID", referencedColumnName = "E_ID")
        @XmlIDREF
        private Employee contact;
    

    The @JoinColumn annotation identifies the column that will contain the foreign key (name="E_ID") and the column referenced by the foreign key (referencedColumnName = "E_ID"). The @XmlIDREF annotation indicates that this will be the primary key for the corresponding table.

  3. Map the return direction—that is, the employee property on PhoneNumber —by inserting the @OneToMany and @XmlInverseMapping annotations into the Address entity:

        @OneToMany(mappedBy="contact")
        @XmlInverseReference(mappedBy="contact")
        private List<PhoneNumber> contactNumber;
    

    The mappedBy field for both annotations indicates that this relationship is owned by the contact field.

The entities should look like those shown in Example 15-1 and Example 15-3.

Mapping a Many-to-Many Shared Reference Relationship

The @ManyToMany annotation indicates that one or more instances of the source entity are able to refer to one or more target entity instances. Since the relationship between Department and Employee is bi-directional, this example again uses the EclipseLink's @XmlInverseReference annotation to represent the back-pointer.

To map a many-to-many shared reference relationship, do the following:

  1. Ensure that the accessor type FIELD has been defined at the package level, as described in Task 1: Define the Accessor Type and Import Classes.

  2. Create a Department entity by inserting the following code:

    @Entity
    public class Department {
    
  3. Under this entity define the many-to-many relationship and the entity's join table by inserting the following code:

        @ManyToMany
        @JoinTable(name="DEPT_EMP", joinColumns = 
            @JoinColumn(name="D_ID", referencedColumnName = "D_ID"), 
                inverseJoinColumns = @JoinColumn(name="E_ID", 
                    referencedColumnName = "E_ID"))
    

    This code creates a join table called DEPT_EMP and identifies the column that will contain the foreign key (name="E_ID") and the column referenced by the foreign key (referencedColumnName = "E_ID"). Additionally, it identifies the primary table on the inverse side of the association.

  4. Complete the initial mapping—in this case, the Department property employee—and make it a foreign key for this entity by inserting the following code:

        @XmlIDREF
        private List<Employee> member;
    
  5. In the Employee entity created in Mapping a One-to-One and Embedded Relationship, specifying that eId is the primary key for JPA (@Id annotation), and for JAXB (@XmlID annotation) by inserting the following code:

        @Id
        @Column(name="E_ID")
        @XmlID
        private BigDecimal eId;
     
    
  6. Still within the Employee entity, complete the return mapping by inserting the following code:

        @ManyToMany(mappedBy="member")
        @XmlInverseReference(mappedBy="member")
        private List<Department> team;
    

The entities should look like those shown in Example 15-1 and Example 15-4.

JPA Entities

Once the mappings are created, the entities should look like those in the following examples:


NoteNote:

In order to save space, package names, import statements, and the get/set methods have been omitted from the code examples. All examples use standard JPA annotations.


Example 15-1 Employee Entity

@Entity
public class Employee {
 
    @Id
    @Column(name="E_ID")
    private BigDecimal eId;
 
    private String name;
 
    @OneToOne(mappedBy="resident")
    private Address residence;
 
    @OneToMany(mappedBy="contact")
    private List<PhoneNumber> contactNumber;
 
    @ManyToMany(mappedBy="member")
    private List<Department> team;
 
}

Example 15-2 Address Entity

@Entity
public class Address {
 
    @Id
    @Column(name="E_ID", insertable=false, updatable=false)
    private BigDecimal eId;
 
    private String city;
 
    private String street;
 
    @OneToOne
    @JoinColumn(name="E_ID")
    private Employee resident;
 
}

Example 15-3 PhoneNumber Entity

@Entity
@Table(name="PHONE_NUMBER")
public class PhoneNumber {
 
    @Id
    @Column(name="P_ID")
    private BigDecimal pId;
 
    @ManyToOne
    @JoinColumn(name="E_ID", referencedColumnName = "E_ID")
    private Employee contact;
 
    private String num;
 
}

Example 15-4 Department Entity

@Entity
public class Department {
 
    @Id
    @Column(name="D_ID")
    private BigDecimal dId;
 
    private String name;
 
    @ManyToMany
    @JoinTable(name="DEPT_EMP", joinColumns = 
        @JoinColumn(name="D_ID", referencedColumnName = "D_ID"), 
            inverseJoinColumns = @JoinColumn(name="E_ID", 
                referencedColumnName = "E_ID"))
    private List<Employee> member;
 
}

Binding Compound Primary Keys to XML

When a JPA entity has compound primary keys, you can bind it by using JAXB annotations and certain EclipseLink extensions, as shown in the following example.

Task1: Define the XML Accessor Type

Define the accessor type as FIELD, as described in Task 1: Define the Accessor Type and Import Classes

Task 2: Create the Target Object

To create the target object, do the following:

  1. Create an Employee entity with a composite primary key class called EmployeeID to map to multiple fields or properties of the entity:

    @Entity
    @IdClass(EmployeeId.class)
    public class Employee {
    
  2. Specify the first primary key, eId, of the entity and map it to a column:

        @Id
        @Column(name="E_ID")
        @XmlID
        private BigDecimal eId;
    
  3. Specify the second primary key, country. In this instance, you need to use @XmlKey to identify the primary key because only one property— eId—can be annotated with the @XmlID.

        @Id
        @XmlKey
        private String country;
    

    The @XmlKey annotation marks a property as a key that will be referenced by using a key-based mapping via the @XmlJoinNode annotation in the source object. This is similar to the @XmlKey annotation except it doesn't require the property be bound to the schema type ID. This is a typical application of the @XmlKey annotation.

  4. Create a many-to-one mapping of the Employee property on PhoneNumber by inserting the following code:

        @OneToMany(mappedBy="contact")
        @XmlInverseReference(mappedBy="contact")
        private List<PhoneNumber> contactNumber;
    

The Employee entity should look like Example 15-5

Example 15-5 Employee Entity with Compound Primary Keys

@Entity
@IdClass(EmployeeId.class)
public class Employee {
 
    @Id
    @Column(name="E_ID")
    @XmlID
    private BigDecimal eId;
 
    @Id
    @XmlKey
    private String country;
 
    @OneToMany(mappedBy="contact")
    @XmlInverseReference(mappedBy="contact")
    private List<PhoneNumber> contactNumber;
 
}

public class EmployeeId {
    public BigDecimal eId;
    public String country;
 
    public EmployeeId(BigDecimal eId, String country) {
        this.id = id;
        this.country = country;;
    }
 
    public boolean equals(Object other) {
        if (other instanceof EmployeeId) {
            final EmployeeId otherEmployeeId = (EmployeeId) other;
            return (otherEmployeeId.eId.equals(eId) && otherEmployeeId.country.equals(country));
        }
 
    return false;
    }
}

Task 3: Create the Source Object

This Task creates the source object, the PhoneNumber entity. Because the target object has a compound key, we need to use the EclipseLink's @XmlJoinNodes annotation to set up the mapping.

To create the source object:

  1. Create the PhoneNumber entity:

    @Entity
    public class PhoneNumber {
    
  2. Create a many-to-one relationship and define the join columns:

     @ManyToOne
        @JoinColumns({
            @JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
            @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
       })
    
  3. Set up the mapping by using the EclipseLink's @XmlJoinNodes annotation

    @XmlJoinNodes( {
            @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"),
            @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()")
        })
    
  4. Define the contact property:

    private Employee contact;
     
    }
    

The target object should look like Example 15-6.

Example 15-6 PhoneNumber Entity

@Entity
public class PhoneNumber {
 
    @ManyToOne
    @JoinColumns({
        @JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
        @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
    })
    @XmlJoinNodes( {
        @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"),
        @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()")
    })
    private Employee contact;
 
}

Binding Embedded ID Classes to XML

An embedded ID defines a separate Embeddable Java class to contain the entity's primary key. It is defined through the @EmbeddedId annotation.The embedded ID's Embeddable class must define each id attribute for the entity using basic mappings. All attributes in the embedded Id's Embeddable are assumed to be part of the primary key. This exercise shows how to derive an XML representation from a set of JPA entities using JAXB when a JPA entity has an embedded ID class.

Task1: Define the XML Accessor Type

Define the XML accessor type as FIELD, as described in Task 1: Define the Accessor Type and Import Classes

Task 2: Create the Target Object

The target object is an entity called Employee and contains the mapping for an employee's contact phone number. Creating this target object requires implementing a DescriptorCustomizer interface, so you must include EclipseLink's @XmlCustomizer annotation Also, since the relationship is bidirectional, you must also implement the @XmlInverseReference. annotation.

To create the target object:

  1. Create the Employee entity. Use the @IdClass annotation to specify that the EmployeeID class will be mapped to multiple properties of the entity.

    @Entity
    @IdClass(EmployeeId.class)
    public class Employee {
    }
    
  2. Define the id property and make it embeddable.

        @EmbeddedId
        @XmlPath(".");
        private EmployeeId id;
    
  3. Define a one-to-many mapping—in this case, the employee property on PhoneNumber. Because the relationship is bi-directional, use @XmlInverseReference to define the return mapping. Both of these relationships will be owned by the contact field, as indicated by the mappedBy argument.

        @OneToMany(mappedBy="contact")
        @XmlInverseReference(mappedBy="contact")
        private List<PhoneNumber> contactNumber;
    

The completed target object should look like Example 15-7.

Example 15-7 Employee Entity as Target Object

@Entity
@IdClass(EmployeeId.class)
@XmlCustomizer(EmployeeCustomizer.class)
public class Employee {
 
    @EmbeddedId
    private EmployeeId id;
 
    @OneToMany(mappedBy="contact")
    @XmlInverseReference(mappedBy="contact")
    private List<PhoneNumber> contactNumber;
 
}

Task 3: Create the Source Object

The source object in this example has a compound key, so you must mark the field @XmlTransient to prevent a key from being mapped by itself. Use EclipseLink's @XmlCustomizer annotation to set up the mapping.

To create the source object, do the following:

  1. Create the PhoneNumber entity.

    @Entity
    public class PhoneNumber {
    }
    
  2. Create a many-to-one mapping and define the join columns.

    @ManyToOne
        @JoinColumns({
            @JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
            @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
        })
    
  3. Define the XML nodes for the mapping, using the EclipseLink @XmlJoinNodes annotation extension. If the target object had a single ID, you would use the @XmlIDREF annotation.

    @XmlJoinNodes( {        @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"),        @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()")    })
    private Employee contact;

The completed PhoneNumber class should look like Example 15-8.

Example 15-8 PhoneNumber Class as Source Object

@Entity
public class PhoneNumber {
 
    @ManyToOne
    @JoinColumns({
        @JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
        @JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
    })
    @XmlJoinNodes( {
        @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"),
        @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()")
    })
    private Employee contact;
 
}

Task 5: Implement the DescriptorCustomizer as PhoneNumberCustomizer Class

Code added in Task 4 indicated the need to create the XMLObjectReferenceMappings to the new values. This requires to implementing the DescriptorCustomizer as the PhoneNumberCustomizer and adding the multiple key mappings. To do this:

  1. Implement DescriptorCustomizer as PhoneNumberCustomizer. Be sure to import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping:

    import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
     
    public class PhoneNumberCustomizer implements DescriptorCustomizer {
    
  2. In the customize method, update the following mappings:

    • contactMapping.setAttributeName to "contact".

    • contactMapping.addSourceToTargetKeyFieldAssociation to "contact/@eID", "eId/text()".

    • contactMapping.addSourceToTargetKeyFieldAssociation to "contact/@country", "country/text()".

PhoneNumberCustomizer should look like Example 15-9.

Example 15-9 PhoneNumber Customizer with Updated Key Mappings

import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLObjectReferenceMapping;
 
public class PhoneNumberCustomizer implements DescriptorCustomizer {
 
    public void customize(ClassDescriptor descriptor) throws Exception {
        XMLObjectReferenceMapping contactMapping = new XMLObjectReferenceMapping();
        contactMapping.setAttributeName("contact");
        contactMapping.setReferenceClass(Employee.class);
        contactMapping.addSourceToTargetKeyFieldAssociation("contact/@eID", "eId/text()");
        contactMapping.addSourceToTargetKeyFieldAssociation("contact/@country", "country/text()");
        descriptor.addMapping(contactMapping);
    }
 
}

Using the EclipseLink XML Binding Document

As demonstrated in the preceding examples, EclipseLink implements the standard JAXB annotations to map JPA entities to an XML representation. You can also express metadata by using the EclipseLink XML Bindings document. Not only can you use XML bindings to separate your mapping information from your actual Java class but you can also use it for more advanced metadata tasks such as:

  • Augmenting or overriding existing annotations with additional mapping information.

  • Specifying all mapping information externally, without using any Java annotations.

  • Defining your mappings across multiple Bindings documents.

  • Specifying "virtual" mappings that do not correspond to concrete Java fields

For more information about using the XML Bindings document, see XML Bindings in the JAXB/MOXy documentation at http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy/Runtime/XML_Bindings.

Comments powered by Disqus