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); } }