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