Oracle® Fusion Middleware Solution Guide for Oracle TopLink 11g Release 1 (11.1.1) Part Number E25034-01 |
|
|
PDF · Mobi · ePub |
This chapter describes how to use JPA with the Java Architecture for XML Binding (JAXB)—the Java EE standard for mapping POJOs (Plain Old Java Objects) to XML—and its Mapping Objects to XML (MOXy) extensions to map JPA entities to XML. Mapping JPA entities to XML is useful when you want to create a data access service with Java API for RESTfule Web Services (JAX-RS), Java API for XML Web Services (JAX-WS), or Spring.
This chapter contains the following topics:
This chapter demonstrates some typical techniques for mapping JPA entities to XML. Working with the examples that follow requires some understanding of such high-level JPA-to-XML mapping concepts, such as JAXB, MOXy, XML binding, and how to override JAXB annotations. The following sections will give you a basic understanding of these concepts:
XML binding is how you represent information in an XML document as an object in computer memory. This allows applications to access the data in the XML from the object rather than using the Domain Object Model (DOM), the Simple API for XML (SAX) or the Streaming API for XML (StAX) to retrieve the data from a direct representation of the XML itself. When binding, JAXB applies a tree structure to the graph of JPA entities. Multiple tree representations of a graph are possible and will depend on the root object chosen and the direction the relationships are traversed.
You can find examples of XML binding with JAXB in Section 7.2, "Binding JPA Entities to XML".
JAXB is a Java API that allows a Java program to access an XML document by presenting that document to the program in a Java format. This process, called binding, represents information in an XML document as an object in computer memory. In this way, applications can access the data in the XML from the object rather than using the Domain Object Model (DOM) or the Streaming API for XML (SAX) to retrieve the data from a direct representation of the XML itself. Usually, an XML binding is used with JPA entities to create a data access service by leveraging a JAX-WS or JAX-RS implementation. Both of these Web Service standards use JAXB as the default binding layer. This service provides a means to access data exposed by JPA across computers, where the client computer might or might not be using Java.
JAXB uses an extended set of annotations to define the binding rules for Java-to-XML mapping. These annotations are subclasses of the javax.xml.bind.
*
packages in the TopLink API. For more information on these annotations, see Oracle Fusion Middleware Java API Reference for EclipseLink.
For more information about JAXB, see "Java Architecture for XML Binding (JAXB)" at:
MOXy is TopLink's JAXB implementation. It allows you to map a POJO model to an XML schema, greatly enhancing your ability to create JPA-to-XML mappings. MOXy supports all the standard JAXB annotations in the javax.xml.bind.annotation
package plus has its own extensions in the org.eclipse.persistence.oxm.annotations
package. You can use these latter annotations in conjunction with the standard annotations to extend the utility of JAXB. Because MOXy represents the optimal JAXB implementation, you still implement it whether or not you explicitly use any of its extensions. MOXy offers these benefits:
It allows you to map your own classes to your own XML schema, a process called "Meet in the Middle Mapping". This avoids static coupling of your mapped classes with a single XML schema,
It offers specific features, such as compound key mapping and mapping relationships with back-pointers to address critical JPA-to-XML mapping issues.
It allows you to map your existing JPA models to industry standard schema.
It allows you to combine MOXy mappings and Oracle TopLink's persistence framework to interact with your data through JCA.
It offers superior performance in several scenarios.
For more information on MOXy, see the MOXy FAQ at:
JAXB/MOXy is not always the most effective way to map JPA to XML. For example, you would not use JAXB if:
You want to specify metadata for a third-party class but do not have access to the source.
You want to map an object model to multiple XML schemas, because JAXB rules preclude applying more than one mapping by using annotations.
Your object model already contains too many annotations—for example, from such services as JPA, Spring, JSR-303, and so on—and you want to specify the metadata elsewhere.
Under these and similar circumstances, you can use an XML data representation by exposing the eclipselink_oxm.xml
file.
XML metadata works in two modes:
It adds to the metadata supplied by annotations. This is useful when:
Annotations define version one of the XML representation, and you use XML metadata to tweak the metadata for future versions.
You use the standard JAXB annotations, and use the XML metadata for the MOXy extensions. In this way you don't introduce new compile time dependencies in the object model.
It completely replaces the annotation metadata, which is useful when you want to map to different XML representations.
To see how to use XML data representation, see Section 7.4, "Using XML Metadata Representation to Override JAXB Annotations"
The following examples demonstrate how to bind JPA entities to XML by using JAXB annotations. For more information on binding, see Section 7.1.1, "Understanding XML Binding"; for more information about JAXB, see Section 7.1.2, "Understanding JAXB"
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 Section 7.2, "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.
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 packages:
@XmlAccessorType(XmlAccessType.FIELD) package com.example.model; import javax.xml.bind.annotation.XmlAccessType; import javax.xml.bind.annotation.XmlAccessorType;
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.
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-on-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 TopLink extension @XmlInverseReference
to represent the back-pointer.
To create the one-to-one and embedded mapping:
Ensure that the accessor type FIELD
has been defined at the package level, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".
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.
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 7-1 and Example 7-2.
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 TopLink extension @XmlInverseReference
to map the back-pointer.
To create a one-to-many mapping:
Ensure that the accessor type FIELD
has been defined at the package level, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".
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.
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 7-1 and Example 7-3.
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 TopLink 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.
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:
Ensure that the accessor type FIELD
has been defined at the package level, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".
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.
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 7-1 and Example 7-3.
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 TopLink's @XmlInverseReference
annotation to represent the back-pointer.
To map a many-to-many shared reference relationship, do the following:
Ensure that the accessor type FIELD
has been defined at the package level, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages".
Create a Department
entity by inserting the following code:
@Entity public class Department {
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.
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;
In the Employee
entity created in Section 7.2.1.2.1, "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;
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 7-1 and Example 7-4.
Once the mappings are created, the entities should look like those in the following examples:
Note:
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 7-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 7-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 7-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 7-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;
}
When a JPA entity has compound primary keys, you can bind it by using JAXB annotations and certain Oracle TopLink extensions, as shown in the following example.
Define the accessor type as FIELD
, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages"
To create the target object, do the following:
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 {
Specify the first primary key, eId, of the entity and map it to a column:
@Id @Column(name="E_ID") @XmlID private BigDecimal eId;
Specify the second primary key, country. In this instance, you need to use @XmlKey
to identify the primary key because only one property—here, eId
—can be annotated with the @XmlID.Cre
@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.
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 7-5
Example 7-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; }
This Task creates the source object, the PhoneNumber
entity. Because the target object has a compound key, we need to use the Oracle TopLink's @XmlJoinNodes
annotation to set up the mapping.
To create the source object:
Create the PhoneNumber
entity:
@Entity public class PhoneNumber {
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") })
Set up the mapping by using the Oracle TopLink's @XmlJoinNodes
annotation
@XmlJoinNodes( { @XmlJoinNode(xmlPath="contact/id/text()", referencedXmlPath="id/text()"), @XmlJoinNode(xmlPath="contact/country/text()", referencedXmlPath="country/text()") })
Define the contact
property:
private Employee contact; }
The target object should look like Example 7-6.
Example 7-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; }
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.
Define the XML accessor type as FIELD
, as described in Section 7.2.1.1, "Task 1: Define the Accessor Type and Import Packages"
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 TopLink's @XmlCustomizer
annotation Also, since the relationship is bidirectional, you must also implement the @XmlInverseReference
. annotation.
To create the target object:
Create the Employee
entity. Use the @IdClass
annotation to specify that the EmployeeID
class will be mapped to multiple properties of the entity and use the @XmlCustomizer
annotation to indicate that the class EmployeeCustomizer
will implement the DescriptorCustomizer
interface (see Section 7.2.3.3, "Task 3: Implement the DescriptorOrganizer as EmployeeCustomizer Class").
@Entity
@IdClass(EmployeeId.class)
public class Employee {
Define the id
property and make it embeddable.
@EmbeddedId @XmlPath("."); private EmployeeId id;
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 7-7.
In Task 2: Create the Target Object, DescriptorCustomizer
was implemented as the class EmployeeCustomizer
. This allows changing the XML Path (XPath) on the mapping for the id
property to either self or "." and then specifying the XPath to the XML nodes that represent the ID. To do this:
Implement the DescriptorOrganizer
class as EmployeeOrganizer
.
import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping;
public class EmployeeCustomizer implements DescriptorCustomizer {
Specify the XPath to the XML nodes which represent the ID:
descriptor.addPrimaryKeyFieldName("eId/text()"); descriptor.addPrimaryKeyFieldName("country/text()");
The EmployeeCustomizer
class should look like Example 7-8.
Example 7-8 EmployeeCustomizer Class with Updated XPath Information
import org.eclipse.persistence.config.DescriptorCustomizer; import org.eclipse.persistence.descriptors.ClassDescriptor; import org.eclipse.persistence.oxm.mappings.XMLCompositeObjectMapping; public class EmployeeCustomizer implements DescriptorCustomizer { descriptor.addPrimaryKeyFieldName("eId/text()"); descriptor.addPrimaryKeyFieldName("country/text()"); } }
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 TopLink's @XmlCustomizer
annotation to set up the mapping.
To create the source object, do the following:
Create the PhoneNumber
entity and specify another class, PhoneNumberCustomizer
to implement the DescriptorCustomizer
interface.
@Entity
@XmlCustomizer(PhoneNumberCustomizer.class)
public class PhoneNumber {
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") })
Define the contact property. Be sure to use the @XmlTranscient
annotation to prevent this key from being mapped by itself.
@XmlTransient private Employee contact;
The completed PhoneNumber
class should look like Example 7-9.
Example 7-9 PhoneNumber Class as Source Object
@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; }
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:
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 {
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 7-10.
Example 7-10 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); } }
As demonstrated in the preceding examples, TopLink 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 on using the XML Bindings document, see XML Bindings in the JAXB/MOXy documentation at http://wiki.eclipse.org/EclipseLink/UserGuide/MOXy/Runtime/XML_Bindings
.
This section demonstrates several ways to map simple Java values directly to XML text nodes. It includes the following examples:
This example maps the id
property in the Java object Customer
to its XML representation as an attribute of the <customer>
element. The XML will be based on the schema in Example 7-11.
Example 7-11 Example XML Schema
<?xml version="1.0" encoding="UTF-8"?> <xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"> <xsd:element name="customer" type="customer-type"/> <xsd:complexType name="customer-type"> <xsd:attribute name="id" type="xsd:integer"/> </xsd:complexType> </xsd:schema>
The following procedures demonstrate how to map the id
property from the Java object and, alternately, how to represent the value in Oracle TopLink's Object-to-XML Mapping (OXM) metadata format.
The key to creating this mapping from a Java object is the @XmlAttribute
JAXB annotation, which maps the field to the XML attribute. To create this mapping:
Create the object and import javax.xml.bind.annotation.*
:
package example; import javax.xml.bind.annotation.*;
Declare the Customer
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Map the id
property in the Customer
class as an attribute:
@XmlAttribute
private Integer id;
The object should look like Example 7-12.
If you want to represent the mapping in Oracle TopLink's OXM metadata format, you need to use the XML tags defined in the eclipselink-oxm.xml
file and populate them with the appropriate values, as shown in Example 7-13.
Example 7-13 Mapping id as an Attribute in OXM Metadata Format
... <java-type name="Customer"> <xml-root-element name="customer"/> <java-attributes> <xml-attribute java-attribute="id"/> </java-attributes> </java-type> ...
For more information on the OXM metadata format, see Section 7.4, "Using XML Metadata Representation to Override JAXB Annotations".
Oracle TopLink makes it easy for you to map values from a Java object to various kinds of XML text nodes; for example, to simple text nodes, text nodes in a simple sequence, in a subset, or by position. These mappings are demonstrated in the following examples:
You can map a value from a Java object either by using JAXB annotations in the Java object or, alternately, by representing the mapping in TopLink's OXM metadata format.
Assuming the associated schema defines an element called <phone-number>
which accepts a string value, you can use the @XmlValue
annotation to map a string to the <phone-number>
node. Do the following:
Create the object and import javax.xml.bind.annotation.*
:
package example; import javax.xml.bind.annotation.*;
Declare the PhoneNumber
class and use the @XmlRootElement
annotation to make it the root element with the name phone-number. Set the XML accessor type to FIELD
:
@XmlRootElement(name="phone-number") @XmlAccessorType(XmlAccessType.FIELD) public class PhoneNumber {
Insert the @XmlValue
annotation on the line before the number
property in the Customer class to map this value as an attribute:
@XmlValue
private String number;
The object should look like Example 7-14.
If you want to represent the mapping in TopLink's OXM metadata format, you need to use the XML tags defined in the eclipselink-oxm.xml
file and populate them with the appropriate values, as shown in Example 7-15.
You can map a sequence of values, for example a customer's first and last name, as separate elements either by using JAXB annotations or by representing the mapping in TopLink's OXM metadata format. The following procedures illustrate how to map values for a customers' first names and last names
Assuming the associated schema defines the following elements:
<"customer">
of the type customer-type, which itself is defined as a complexType
.
Sequential elements called <"first-name">
and <"last-name">
, both of the type string
.
you can use the @XmlElement
annotation to map values for a customer's first and last name to the appropriate XML nodes. To do so:
Create the object and import javax.xml.bind.annotation.*
:
package example; import javax.xml.bind.annotation.*;
Declare the Customer
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Define the firstname
and lastname
properties and annotate them with the @XmlElement
annotation. Use the name=
argument to customize the XML element name (if you do not explicitly set the name with name=
, the XML element will match the Java attribute name; for example, here the <first-name>
element combination would be specified <firstName> </firstName>
in XML).
@XmlElement(name="first-name") private String firstName; @XmlElement(name="last-name") private String lastName;
The object should look like Example 7-16.
Example 7-16 Customer Object Mapping Values to a Simple Sequence
package example; import javax.xml.bind.annotation.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlElement(name="first-name") private String firstName; @XmlElement(name="last-name") private String lastName; ... }
If you want to represent the mapping in TopLink's OXM metadata format, you need to use the XML tags defined in the eclipselink-oxm.xml
file and populate them with the appropriate values, as shown in Example 7-17.
Example 7-17 Mapping Sequential Attributes in OXM Metadata Format
... <java-type name="Customer"> <xml-root-element name="customer"/> <java-attributes> <xml-element java-attribute="firstName" name="first-name"/> <xml-element java-attribute="lastName" name="last-name"/> </java-attributes> </java-type> ...
You can map values from a Java object to text nodes that are nested as a subelement in the XML document by using JAXB annotations or by representing the mapping in Oracle TopLink's OXM metadata format. For example, if you want to populate <first-name>
and <last-name>
elements, which are sub-elements of a <personal-info>
element under a <customer>
root, you could use the following procedures to achieve these mappings.
Assuming the associated schema defines the following elements:
<"customer">
of the type customer-type, which itself is defined as a complexTpe.
<personal-info>
Sub-elements of <personal-info>
called <"first-name">
and <"last-name">
, both of the type string
you can use JAXB annotations to map values for a customer's first and last name to the appropriate XML sub-element nodes. Because this example goes beyond a simple element name customization and actually introduces new XML structure, it uses TopLink's @XmlPath
annotation. To achieve this mapping:
Create the object and import javax.xml.bind.annotation.*
and org.eclipse.persistence.oxm.annotations.*
.
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.*;
Declare the Customer
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Define the firstName
and lastName
properties.
Map the firstName
and lastName
properties to the sub-elements defined by the XML schema by inserting the @XmlPath
annotation on the line immediately preceding the property declaration. For each annotation, define the mapping by specifying the appropriate XPath predicate:
@XmlPath("personal-info/first-name/text()") private String firstName; @XmlPath("personal-info/last-name/text()") private String lastName;
The object should look like Example 7-18.
Example 7-18 Customer Object Mapping Properties to Sub-elements
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.*; @XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("personal-info/first-name/text()") private String firstName; @XmlPath("personal-info/last-name/text()") private String lastName; ... }
If you want to represent the mapping in TopLink's OXM metadata format, you need to use the XML tags defined in the eclipselink-oxm.xml
file and populate them with the appropriate values, as shown in Example 7-19.
Example 7-19 Mapping Attributes as Sub-elements in OXM Metadata Format
... <java-type name="Customer"> <xml-root-element name="customer"/> <java-attributes> <xml-element java-attribute="firstName" xml-path="personal-info/first-name/text()"/> <xml-element java-attribute="lastName" xml-path="personal-info/last-name/text()"/> </java-attributes> </java-type> ...
When multiple nodes have the same name, map their values from the Java object by specifying their position in the XML document. Do this by using mapping the values to the position of the attribute rather than the attribute's name. You can do this either by using JAXB annotations or by or by representing the mapping in TopLink's OXM metadata format. In the following example, XML contains two <name>
elements; the first occurrence of name should represent the Customer's first name, the second name their last name.
Assuming an XML schema that defines the following attributes:
<customer>
of the type customer-type, which itself is specified as a complexType
<name>
of the type String
this example again uses the JAXB @XmlPath
annotation to map a customer's first and last names to the appropriate <name>
element. It also uses the @XmlType(propOrder)
annotation to ensure that the elements are always in the proper positions. To achieve this mapping:
Create the object and import javax.xml.bind.annotation.*
and org.eclipse.persistence.oxm.annotations.XmlPath
.
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Declare the Customer
class and insert the @XmlType(propOrder)
annotation with the arguments "firstName"
followed by "lastName"
. Insert the @XmlRootElement
annotation to make Customer
the root element and set the XML accessor type to FIELD
:
@XmlRootElement @XmlType(propOrder={"firstName", "lastName"}) @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Define the properties firstName
and lastName
with the type String
.
Map the properties firstName
and lastName
to the appropriate position in the XML document by inserting the @XmlPath
annotation with the appropriate XPath predicates.
@XmlPath("name[1]/text()") private String firstName; @XmlPath("name[2]/text()") private String lastName;
The predicates, "name[1]/text()"
and "name[2]/text()"
indicate the <name>
element to which that specific property will be mapped; for example, "name[1]/text"
will map the firstName
property to the first <name>
element.
The object should look like Example 7-20.
Example 7-20 Customer Object Mapping Values by Position
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement @XmlType(propOrder={"firstName", "lastName"}) @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("name[1]/text()") private String firstName; @XmlPath("name[2]/text()") private String lastName; ... }
For more information on using XPath predicates, see Section 7.5, "Using XPath Predicates for Mapping".
In addition to using Java annotations, TopLink provides an XML mapping configuration file called eclipselink-oxm.xml
that you can use in place of or to override JAXB annotations in the source with an XML representation of the metadata. In addition to allowing all of the standard JAXB mapping capabilities it also includes advanced mapping types and options.
An XML metadata representation is useful when:
You cannot modify the domain model because, for example, it come from a third party).
You do not want to introduce compile dependencies on JAXB APIs (if you are using a version of Java that predates Java SE 6).
You want to apply multiple JAXB mappings to a domain model (you are limited to one representation with annotations).
Your object model already contains so many annotations from other technologies that adding more would make the class unreadable.
This section demonstrates how to use eclipselink-oxm.xml
to override JAXB annotations
Note:
While using this mapping file enables many advanced features, it might prevent you from porting it to other JAXB implementationsFirst, update the XML mapping file to expose the eclipselink_oxm_2_3.xsd
. schema. Example 7-21 shows how to modify the <xml-bindings>
element in the mapping file to point to the correct namespace and leverage the schema. Each Java package can have one mapping file.
Example 7-21 Updating XML Binding Information in the Mapping File
<?xml version="1.0"?> <xml-bindings xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.eclipse.org/eclipselink/xsds/persistence/oxm http://www.eclipse.org/eclipselink/xsds/eclipselink_oxm_2_3.xsd" version="2.3"> </xml-bindings>
Next, pass the mapping file to JAXBContext
in your object:
Specify the externalized metadata by inserting this code:
Map<String, Source> metadata = new HashMap<String,Source>(); metadata.put("example.order", new StreamSource("order-metadata.xml")); metadata.put("example.customer", new StreamSource("customer-metadata.xml"));
Create the properties object to pass to the JAXBContext
. For this example:
Map<String,Object> properties = new HashMap<String,Object>(); properties.put(JAXBContextFactory.ECLIPSELINK_OXM_XML_KEY, metadata);
Create the JAXBContext
. For this example:
JAXBContext.newInstance("example.order:example.customer", aClassLoader, properties);
You must use MOXy as your JAXB implementation. To do so, do the following:
Open a jaxb.properties
file and add the following line:
javax.xml.bind.context.factory=org.eclipse.persistence.jaxb.JAXBContextFactory
Copy the jaxb.properties
file to the package that contains your domain classes.
This section demonstrates how the TopLink MOXy API uses XPath predicates to define an expression that specifiers the XML element's name. An XPath predicate is an expression that defines a specific object-to-XML mapping. As shown in previous examples, by default, JAXB will use the Java field name as the XML element name.
This section contains the following subsections:
As described above, an XPath predicate is an expression that defines a specific object-to-XML mapping when standard annotations
re not sufficient. For example, the following snippet of XML shows a <data>
element with two <node>
sub-elements. If you wanted to create this mapping in a Java object, you would need to specify an XPath predicate for each <node>
sub-element; for example, Node[2]
in the following Java:
<java-attributes>
<xml-element java-attribute="node" xml-path="node[1]/ABC"/>
<xml-element java-attribute="node" xml-path="node[2]/DEF"/>
</java-attributes>
would match the second occurrence of the node element ("DEF"
) in the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<data>
<node>ABC</node>
<node>DEF</node>
</data>
Thus, by using the XPath predicate, you can use the same attribute name for a different attribute value.
This mapping technique is described in Section 7.3.2.4, "Mapping Values to a Text Node by Position".
Beginning with EclipseLink MOXy 2.3, you can also map to an XML element based on an Attribute value. In this exercise, you will annotate the JPA entity to render the XML document shown in Example 7-22. Note that all of the XML elements are named node but are differentiated by the value of their name attribute.
Example 7-22
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="address"> <node name="street">123 A Street</node> </node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
To attain this mapping, you need to declare three classes, Name
, Address
, and PhoneNumber
and then use an XPath in the form of element-name
[@
attribute-name
='
value
']
to map each Java field.
To create the Customer
class entity:
Import the necessary JPA packages by adding the following code:
import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Declare the Customer
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Declare local to the Customer
class these properties:
firstName
(String type)
lastName
(String)
Address
(Address)
For each property, set the Xpath predicate by preceding the property declaration with the annotation @XmlPath(
element-name
[@
attribute-name
='
value
'])
; for example, for firstName
, you would set the XPath predicate with this statement:
@XmlPath("node[@name='first-name']/text()")
Also local to the Customer
class, declare the phoneNumber
property as a List<PhoneNumber>
type and assign it the value new ArrayList<PhoneNumber>()
.
The Customer
class should look like the snippet in Example 7-23.
Example 7-23 Customer Object Mapping to an Attribute Value
package example; import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath; @XmlRootElement(name="node") @XmlAccessorType(XmlAccessType.FIELD) public class Customer { @XmlPath("node[@name='first-name']/text()") private String firstName; @XmlPath("node[@name='last-name']/text()") private String lastName; @XmlPath("node[@name='address']") private Address address; @XmlPath("node[@name='phone-number']") private List<PhoneNumber> phoneNumbers = new ArrayList<PhoneNumber>(); ... }
To create the Address
class, do the following:
Import the necessary JPA packages by adding the following code:
import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Declare the Address
class and set the XML accessor type to FIELD
:
@XmlAccessorType(XmlAccessType.FIELD)
public class Address {
This instance does not require the @XmlRootElement
annotation as in the previous Tasks because the Address
class is root not a root element in the XML document.
Declare local to the Address
class the String
property street
. Set the XPath predicate by preceding the property declaration with the annotation @XmlPath("node[@name='street']/text()")
.
The Address
class should look like Example 7-24.
To create the PhoneNumber
entity:
Import the necessary JPA packages by adding the following code:
import javax.xml.bind.annotation.*; import org.eclipse.persistence.oxm.annotations.XmlPath;
Declare the PhoneNumber
class and use the @XmlRootElement
annotation to make it the root element. Set the XML accessor type to FIELD
:
@XmlRootElement @XmlAccessorType(XmlAccessType.FIELD) public class Customer {
Create the type and string properties and define their mapping as attributes under the PhoneNumber root element by using the @XmlAttribute
. annotation.
@XmlAttribute private String type; @XmlValue private String number;
The PhoneNumber
object should look like Example 7-25.
A "self" mapping occurs on one-to-one mappings when you set the target object's XPath to "." (dot) so the data from the target object appears inside the source object's XML element. This exercise uses the example in Section 7.5.3, "Mapping Based on an Attribute Value" to map the Address information to appear directly under the customer element and not wrapped in its own element.
To create the self mapping:
Repeat Tasks 1 and 2 in Section 7.5.3.1, "Task 1: Create the Customer Entity".
Declare local to the Customer
class these properties:
firstName
(String type)
lastName
(String)
Address
(Address)
For the firstName
and lastName
properties, set the XmlPath annotation by preceding the property declaration with the annotation @XmlPath(
element-name
[@
attribute-name
='
value
'])
; for example, for firstName
, you would set the XPath predicate with this statement:
@XmlPath("node[@name='first-name']/text()")
For the address
property, set @XmlPath
to "." (dot):
@XmlPath(".") private Address address;
Also local to the Customer
class, declare the phoneNumber
property as a List<PhoneNumber>
type and assign it the value new ArrayList<PhoneNumber>()
.
The rendered XML for the Customer entity would look like Example 7-26.
Example 7-26 XML Node with Self-Mapped Address Element
<?xml version="1.0" encoding="UTF-8"?> <node> <node name="first-name">Bob</node> <node name="last-name">Smith</node> <node name="street">123 A Street</node> <node name="phone-number" type="work">555-1111</node> <node name="phone-number" type="cell">555-2222</node> </node>
Dynamic JAXB/MOXy allows you to bootstrap a JAXBContext
from a variety of metadata sources and use familiar JAXB APIs to marshal and unmarshal data, without requiring compiled domain classes. This is an enhancement over static JAXB, because now you can update the metadata without having to update and recompile the previously-generated Java source code.
The benefits of using dynamic JAXB/MOXy entities are:
Instead of using actual Java classes (for example, Customer.class
, Address.class
, and so on), the domain objects are subclasses of the DynamicEntity
.
Dynamic entities offer a simple get(propertyName)
/set(propertyName propertyValue)
API to manipulate their data.
Dynamic entities have an associated DynamicType
, which is generated in-memory, when the metadata is parsed.
The following Tasks demonstrate how to use dynamic JAXB:
This example demonstrates how to bootstrap a dynamic JAXBContext
from an XML Schema.
Use the DynamicJAXBContextFactory
to create a dynamic JAXBContext
. Example 7-27 to bootstrap a DynamicJAXBContext
from the customer.xsd
schema (Example 7-28) by using createContextFromXSD()
.
Example 7-27 Specifying the Input Stream and Creating the DynamicJAXBContext
import java.io.FileInputStream; import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContext; import org.eclipse.persistence.jaxb.dynamic.DynamicJAXBContextFactory; public class Demo { public static void main(String[] args) throws Exception { FileInputStream xsdInputStream = new FileInputStream("src/example/customer.xsd"); DynamicJAXBContext jaxbContext = DynamicJAXBContextFactory.createContextFromXSD(xsdInputStream, null, null, null);
The first parameter represents the XML schema itself and must be in one of the following forms: java.io.InputStream
, org.w3c.dom.Node
, or javax.xml.transform.Source
.
Example 7-28 shows the customer.xsd
schema that represents the metadata for the dynamic JAXBContext you are bootstrapping.
Example 7-28 Sample XML Schema Document
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.org" targetNamespace="http://www.example.org" elementFormDefault="qualified"> <xsd:complexType name="address"> <xsd:sequence> <xsd:element name="street" type="xsd:string" minOccurs="0"/> <xsd:element name="city" type="xsd:string" minOccurs="0"/> </xsd:sequence> </xsd:complexType> <xsd:element name="customer"> <xsd:complexType> <xsd:sequence> <xsd:element name="name" type="xsd:string" minOccurs="0"/> <xsd:element name="address" type="address" minOccurs="0"/> </xsd:sequence> </xsd:complexType> </xsd:element> </xsd:schema>
To bootstrap DynamicJAXBContext
from an XML schema that contains imports of other schemas, you need to configure an org.xml.sax.EntityResolver
to resolve the locations of the imported schemas and pass the EntityResolver
to DynamicJAXBContextFactory
.
The following example shows two schema documents, customer.xsd
(Example 7-29) and address.xsd
Example 7-30). You can see that customer.xsd
imports address.xsd
by using the statement:
<xsd:import namespace="http://www.example.org/address" schemaLocation="address.xsd"/>
Example 7-29 customer.xsd
<?xml version="1.0" encoding="UTF-8"?>
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:add="http://www.example.org/address"
xmlns="http://www.example.org/customer"
targetNamespace="http://www.example.org/customer"
elementFormDefault="qualified">
<xsd:import namespace="http://www.example.org/address" schemaLocation="address.xsd"/>
<xsd:element name="customer">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:string" minOccurs="0"/>
<xsd:element name="address" type="add:address" minOccurs="0"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Example 7-30 address.xsd
<?xml version="1.0" encoding="UTF-8"?> xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="http://www.example.org/address" targetNamespace="http://www.example.org/address" elementFormDefault="qualified"> <xsd:complexType name="address"> <xs:sequence> <xs:element name="street" type="xs:string"/> <xs:element name="city" type="xs:string"/> </xs:sequence> </xsd:complexType> </xsd:schema>
If you want to bootstrap DynamicJAXBContext
from the customer.xsd
schema, you need to pass an entity resolver. Do the following:
To resolve the locations of the imported schemas, you need to implement an entityResolver
by supplying the code shown in Example 7-31.
Example 7-31 Implementing an EntityResolver
class MyEntityResolver implements EntityResolver { public InputSource resolveEntity(String publicId, String systemId) throws SAXException, IOException { // Imported schemas are located in ext\appdata\xsd\ // Grab only the filename part from the full path String filename = new File(systemId).getName(); // Now prepend the correct path String correctedId = "ext/appdata/xsd/" + filename; InputSource is = new InputSource(ClassLoader.getSystemResourceAsStream(correctedId)); is.setSystemId(correctedId); return is; } }
After you implement your DynamicJAXBContext
, pass the EntityResolver
, as shown in Example 7-32.
You might see the following exception when importing another schema:
Internal Exception: org.xml.sax.SAXParseException: schema_reference.4: Failed to read schemadocument '<imported-schema-name>', because 1) could not find the document; 2) the document couldnot be read; 3) the root element of the document is not <xsd:schema>.
To work around this exception, disable XJC's schema correctness check by setting the noCorrectnessCheck
Java property. You can set this property one of two ways:
From within the code, by adding this line:
System.setProperty("com.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.noCorrectnessCheck", "true")
From the command line, by using this command:
-Dcom.sun.tools.xjc.api.impl.s2j.SchemaCompilerImpl.noCorrectnessCheck=true
Use your application's current class loader as the classLoader
parameter. This parameter verifies that specified classes exist before new DynamicTypes
are generated. In most cases you can pass null
for this parameter and use Thread.currentThread().getContextClassLoader()
instead.
This example shows how to create dynamic entities and marshal then to XML.
Use the DynamicJAXBContext
to create instances of DynamicEntity
. The entity and property names correspond to the class and property names—in this case, the customer
and address
—that would have been generated if you had used static JAXB.
Example 7-33 Creating the Dynamic Entity
DynamicEntity customer = jaxbContext.newDynamicEntity("org.example.Customer"); customer.set("name", "Jane Doe"); DynamicEntity address = jaxbContext.newDynamicEntity("org.example.Address"); address.set("street", "1 Any Street").set("city", "Any Town"); customer.set("address", address);
The marshaller obtained from the DynamicJAXBContext
is a standard marshaller and can be used normally to marshal instances of DynamicEntity.
Example 7-34 Standard Dynamic JAXB Marshaller
Marshaller marshaller = jaxbContext.createMarshaller();
marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);marshaller.marshal(customer, System.out);
Example 7-35 show resultant XML document:
In this example shows how to unmarshal from XML the dynamic entities you created in Task 2: Create Dynamic Entities and Marshal Them to XML. The XML in reference is shown in Example 7-35.
The Unmarshaller obtained from the DynamicJAXBContext
is a standard unmarshaller, and can be used normally to unmarshal instances of DynamicEntity
.
Next, specify which data in the dynamic entity to obtain. Specify this value by using System.out.println()
and passing in the entity name. DynamicEntity
offers property-based data access; for example, get("name")
instead of getName()
:
System.out.println(customer.<String>get("name"));
Instances of DynamicEntity
have a corresponding DynamicType
, which you can use to introspect the DynamicEntity
, as shown in Example 7-37.