This chapter includes the following sections:
EclipseLink supports shared reference keys and foreign keys through:
Single key
Composite Key
Embedded Key Class
To model non-privately-owned relationships, your "target" objects must have IDs (keys) defined, and your "source" object must use these IDs to map the relationship.
Relationships represented with keys use the @XmlID and @XmlIDREF annotations. Although the JAXB specification requires that the property marked with @XmlID be a String, MOXy JAXB does not enforce this restriction.
In Example 7-1, each Employee has one manager but multiple reports.
Example 7-1 Using the @XmlID and @XmlIDREF Annotations
package example;
import javax.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
@XmlAttribute
@XmlID
private Integer id;
@XmlAttribute
private String name;
@XmlIDREF
private Employee manager;
@XmlElement(name="report")
@XmlIDREF
private List<Employee> reports;
...
}
The following example shows how to define this mapping information in EclipseLink's OXM metadata format.
Example 7-2 Sample XML Mapping
...
<java-type name="Employee">
<java-attributes>
<xml-attribute java-attribute="id" type="java.lang.Integer" xml-id="true"/>
<xml-attribute java-attribute="name" type="java.lang.String"/>
<xml-element java-attribute="manager" type="mypackage.Employee" xml-idref="true"/>
<xml-element java-attribute="reports" type="mypackage.Employee" container-type="java.util.ArrayList" xml-idref="true"/>
</java-attributes>
</java-type>
...
This would produce the following XML:
<company>
<employee id="1" name="Jane Doe">
<report>2</report>
<report>3</report>
</employee>
<employee id="2" name="John Smith">
<manager>1</manager>
</employee>
<employee id="3" name="Anne Jones">
<manager>1</manager>
</employee>
</company>
The manager and reports elements contain the IDs of the Employee instances they are referencing.
Because the @XmlIDREF annotation is also compatible with the @XmlList annotation, the Employee object could be modeled as:
Example 7-3 Using the @XmlList Annotation
package example;
import javax.xml.bind.annotation.*;
@XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
@XmlID
@XmlAttribute
private Integer id;
@XmlAttribute
private String name;
@XmlIDREF
private Employee manager;
@XmlIDREF
@XmlList
private List<Employee> reports;
...
}
This would produce the following XML:
<company>
<employee id="1" name="Jane Doe">
<reports>2 3</reports>
</employee>
<employee id="2" name="John Smith">
<manager>1</manager>
</employee>
<employee id="3" name="Anne Jones">
<manager>1</manager>
</employee>
</company>
With JAXB, you can derive an XML representation from a set of JPA entities, when a JPA entity has an embedded ID class.
In Example 7-4, the EmployeeId is the embedded ID of the Employee class:
Example 7-4 Sample Embedded ID
@Entity
public class PhoneNumber {
@ManyToOne
@JoinColumns({
@JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
@JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
})
private Employee contact;
}
@Entity
@IdClass(EmployeeId.class)
public class Employee {
@EmbeddedId
private EmployeeId id;
@OneToMany(mappedBy="contact")
private List<PhoneNumber> contactNumber;
}
@Embeddable
public class EmployeeId {
@Column(name="E_ID")
private BigDecimal eId;
private String country;
}
For the JAXB bindings, the XML accessor type will be set to FIELD for all the model classes. This can be set as a package level JAXB annotation, as shown here:
@XmlAccessorType(XmlAccessType.FIELD) package com.example.model; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType;
Example 7-5 uses the EclipseLink extension @XmlCustomizer which extends the JAXB specification. Because the contact attribute is a bidirectional relationship, it includes the EclipseLink extension @XmlInverseReference.
Example 7-5 Using the @XmlCustomizer Annotation
@Entity
@IdClass(EmployeeId.class)
@XmlCustomizer(EmployeeCustomizer.class)
public class Employee {
@EmbeddedId
private EmployeeId id;
@OneToMany(mappedBy="contact")
@XmlInverseReference(mappedBy="contact")
private List<PhoneNumber> contactNumber;
}
To embed the content of the EmployeeId class in the complex type corresponding to the Employee class, change the XPath on the mapping for the id property to be self or . . Then specify the XPath to the XML nodes which represent the ID.
Example 7-6 Changing the XPath
import org.eclipse.persistence.config.DescriptorCustomizer;
import org.eclipse.persistence.descriptors.ClassDescriptor;
import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping;
public class EmployeeCustomizer implements DescriptorCustomizer {
public void customize(ClassDescriptor descriptor) throws Exception {
XMLCompositeObjectMapping idMapping =
(XMLCompositeObjectMapping) descriptor.getMappingForAttributeName("id");
idMapping.setXPath(".");
descriptor.addPrimaryKeyFieldName("eId/text()");
descriptor.addPrimaryKeyFieldName("country/text()");
}
}
If the target object had a single ID then we would use @XmlIDREF. Since the target object has a compound key, we will mark the field @XmlTransient, and use the EclipseLink extension @XmlCustomizer to set up the mapping.
Example 7-7 Using the @XmlTransient Annotation
@Entity
@XmlCustomizer(PhoneNumberCustomizer.class)
public class PhoneNumber {
@ManyToOne
@JoinColumns({
@JoinColumn(name="E_ID", referencedColumnName = "E_ID"),
@JoinColumn(name="E_COUNTRY", referencedColumnName = "COUNTRY")
})
@XmlTransient
private Employee contact;
}
An XMLObjectReferenceMapping will be created. The mapping will include multiple 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);
}
}
If the objects that you want to map have multi-part keys (that is, a combination of fields that determines uniqueness), you can use EclipseLink's @XmlKey and @XmlJoinNodes to set up this relationship.
One or more @XmlKey annotations can be used to declare the primary keys in a given class. For a single key, either @XmlID or @XmlKey can be used. For composite primary keys, multiple @XmlKey annotations can be used, or a single @XmlID can be combined with one or more @XmlKey annotations.
Note:
Composite Keys can be useful when using JAXB to map JPA entities. For more information see Converting JPA entities to/from XML (via JAXB).
In Example 7-8, each Employee has one manager but multiple reports, and Employees are uniquely identified by the combination of their id and name fields.
Example 7-8 Using the @XmlKey and @XmlJoinNodes Annotations
package example;
import javax.xml.bind.annotation.*;
import org.eclipse.persistence.oxm.annotations.*;
@XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
@XmlID
@XmlAttribute
private Integer id;
@XmlKey
@XmlAttribute
private String name;
@XmlJoinNodes( {
@XmlJoinNode(xmlPath = "manager/@id", referencedXmlPath = "@id"),
@XmlJoinNode(xmlPath = "manager/@name", referencedXmlPath = "@name") })
public Employee manager;
@XmlJoinNodes( {
@XmlJoinNode(xmlPath = "report/@id", referencedXmlPath = "@id"),
@XmlJoinNode(xmlPath = "report/@name", referencedXmlPath = "@name") })
public List<Employee> reports = new ArrayList<Employee>();
...
}
Example 7-9 shows how to define this mapping information in EclipseLink's OXM metadata format.
Example 7-9 Sample XML Mapping
...
<java-type name="Employee">
<java-attributes>
<xml-attribute java-attribute="id" xml-id="true" />
<xml-attribute java-attribute="name" xml-key="true" />
<xml-join-nodes java-attribute="manager">
<xml-join-node xml-path="manager/@id" referenced-xml-path="@id" />
<xml-join-node xml-path="manager/@name" referenced-xml-path="@name" />
</xml-join-nodes>
<xml-join-nodes java-attribute="reports" container-type="java.util.ArrayList">
<xml-join-node xml-path="report/@id" referenced-xml-path="@id" />
<xml-join-node xml-path="report/@name" referenced-xml-path="@name" />
</xml-join-nodes>
</java-attributes>
</java-type>
...
This would produce the following XML:
<company>
<employee id="1" name="Jane Doe">
<report id="2" name="John Smith"/>
<report id="3" name="Anne Jones"/>
</employee>
<employee id="2" name="John Smith">
<manager id="1" name="Jane Doe"/>
</employee>
<employee id="3" name="Anne Jones">
<manager id="1" name="Jane Doe"/>
</employee>
</company>
In order to map bidirectional relationships in EclipseLink MOXy, the back-pointer must be annotated as an @XmlInverseReference. Without this annotation, the cyclic relationship will result in an infinite loop during marshalling.
@XmlInverseReferences must specify the mappedBy attribute, which indicates the property on the opposite side of the relationship.
In Example 7-11, an Employee has a collection of PhoneNumbers, and each PhoneNumber has a back-pointer back to its Employee:
Example 7-10 Using the @XMlInverseReference Annotation
@XmlAccessorType(XmlAccessType.FIELD)
public class Employee {
private String name;
private List<PhoneNumber> phones = new ArrayList<PhoneNumber>();
...
}
@XmlAccessorType(XmlAccessType.FIELD)
public class PhoneNumber {
private String number;
@XmlInverseReference(mappedBy="phones")
private Employee employee;
...
}
Example 7-11 shows how to define this mapping in EclipseLink's OXM metadata format:
Example 7-11 Sample XML Mapping
...
<java-type name="Employee">
<java-attributes>
<xml-element java-attribute="name" type="java.lang.String"/>
<xml-element java-attribute="phones" type="PhoneNumber" container-type="java.util.ArrayList"/>
</java-attributes>
</java-type>
<java-type name="PhoneNumber">
<java-attributes>
<xml-element java-attribute="number" type="java.lang.String"/>
<xml-inverse-reference java-attribute="employee" type="Employee" mapped-by="phones" />
</java-attributes>
</java-type>
...
In addition, when using @XmlInverseReference, it is not necessary to explicitly set the back-pointer in your Java code; EclipseLink will do this for you automatically:
Employee emp = new Employee();
emp.setName("Bob Smith");
PhoneNumber p = new PhoneNumber();
p.setNumber("555-1212");
emp.getPhones().add(p);
// Not Necessary
// p.setEmployee(emp);
@XmlInverseReference back-pointers can be used with the following types of mappings:
One-To-One Relationships (see "Mapping Privately Owned One-to-One Relationships")
One-To-Many Relationships (see "Mapping Privately Owned One-to-Many Relationships")
Single Key Relationships (see "Mapping Single Key Relationships")
Composite Key Relationships (see "Mapping Composite Key Relationships")
@XmlInverseReference can be particularly useful when mapping JPA entities to XML (see "Using XML Bindings")