Dozer: Mapping JAXB Objects to Business/Domain Objects

Dozer is an open source (Apache 2 license) "Java Bean to Java Bean mapper that recursively copies data from one object to another." As this description from its main web page states, it is used to map two JavaBeans instances for automatic data copying between the instances. Although these can be any of the many types of JavaBeans instances, I will focus this post on using Dozer to map JAXB-generated objects to "business data objects" (sometimes referred to as "domain objects").

In Java applications making use of the Java Architecture for XML Binding (JAXB), it is very common for developers to write specific business or domain objects for use in the application itself and only use the JAXB-generated objects for reading (unmarshalling) and writing (marshalling) XML. Although using the JAXB-generated objects themselves as the business/domain objects has some appeal (DRY), there are disadvantages to this approach. JAXB-generated classes do not have toString(), equals(Object), or hashCode() implementations, making these generated classes unsuitable for use in many types of collections, unsuitable for comparison other than identity comparison, and unsuitable for easily logging their contents. Manually editing these generated classes after their generation is tedious and is not conducive to regeneration of the JAXB classes again when even slight changes might be made to the source XSD.

Although JAXB2 Basics can be used to ensure that JAXB-generated classes have some of the common methods needs for use in collections, use in comparisons, and for logging of their contents, a potentially even bigger issue with using JAXB-generated classes as domain/business objects is the tight coupling of business logic to XSD this entails. A schema change in an XSD (such as for version update) typically leads to a different package structure for classes generated from that XSD via JAXB. The different package structure then forces all code that imports those JAXB-generated classes to change their import statements. Content changes to the XSD can have even more dramatic impacts, affecting get/set methods on the JAXB classes that would be strewn throughout the application if the JAXB classes are used for domain/business objects.

Assuming that one decides to not use JAXB-generated classes as business/domain classes, there are multiple ways to map the generated JAXB classes to the classes defining the business/domain objects via a "mapping layer" described in code or configuration. To demonstrate two code-based mapping layer implementations and to demonstrate a Dozer-based mapping layer, I introduce some simple examples of JAXB-generated classes and custom built business/domain classes.

The first part of the example for this post is the XSD from which JAXB'x xjc will be instructed to general classes for marshalling to XML described by that XSD or unmarshalling from XML described by that XSD. The XSD, which is shown next, defines a Person element which can have nested MailingAddress and ResidentialAddress elements and two String attributes for first and last names. Note also that the main namespace is http://marxsoftware.blogspot.com/, which JAXB will use to determine the Java package hierarchy for classes generated from this XSD.

Person.xsd

<?xml version="1.0"?>
<xs:schema version="1.0"
           xmlns:xs="http://www.w3.org/2001/XMLSchema"
           xmlns:marx="http://marxsoftware.blogspot.com/"
           targetNamespace="http://marxsoftware.blogspot.com/"
           elementFormDefault="qualified">
   
   <xs:element name="Person" type="marx:PersonType" />
   
   <xs:complexType name="PersonType">
      <xs:sequence>
         <xs:element name="MailingAddress" type="marx:AddressType" />
         <xs:element name="ResidentialAddress" type="marx:AddressType" minOccurs="0" />
      </xs:sequence>
      <xs:attribute name="firstName" type="xs:string" />
      <xs:attribute name="lastName" type="xs:string" />
   </xs:complexType>
   
   <xs:complexType name="AddressType">
      <xs:attribute name="streetAddress1" type="xs:string" use="required" />
      <xs:attribute name="streetAddress2" type="xs:string" use="optional" />
      <xs:attribute name="city" type="xs:string" use="required" />
      <xs:attribute name="state" type="xs:string" use="required" />
      <xs:attribute name="zipcode" type="xs:string" use="required" />
   </xs:complexType>
   
</xs:schema>

When xjc (the JAXB compiler delivered with Oracle's JDK) is executed against the above XSD, the following four classes are generated in the directory com/blogspot/marxsoftware (derived from the XSD's namespace): AddressType.java, PersonType.java, ObjectFactory.java, and package-info.java.

The next two code listings are of the two main classes of interest (PersonType.java and AddressType.java) generated by JAXB. The primary purpose of showing them here is as a reminder that they lack methods we often need our business/domain classes to have.

JAXB-generated PersonType.java

//
// This file was generated by the JavaTM Architecture for XML Binding(JAXB) Reference Implementation, v2.2.4-2 
// See <a href="http://java.sun.com/xml/jaxb">http://java.sun.com/xml/jaxb</a> 
// Any modifications to this file will be lost upon recompilation of the source schema. 
// Generated on: 2013.12.03 at 11:44:32 PM MST 
//


package com.blogspot.marxsoftware;

import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlType;


/**
 * <p>Java class for PersonType complex type.
 * 
 * <p>The following schema fragment specifies the expected content contained within this class.
 * 
 * <pre>
 * <complexType name="PersonType">
 *   <complexContent>
 *     <restriction base="{http://www.w3.org/2001/XMLSchema}anyType">
 *       <sequence>
 *         <element name="MailingAddress" type="{http://marxsoftware.blogspot.com/}AddressType"/>
 *         <element name="ResidentialAddress" type="{http://marxsoftware.blogspot.com/}AddressType" minOccurs="0"/>
 *       </sequence>
 *       <attribute name="firstName" type="{http://www.w3.org/2001/XMLSchema}string" />
 *       <attribute name="lastName" type="{http://www.w3.org/2001/XMLSchema}string" />
 *     </restriction>
 *   </complexContent>
 * </complexType>
 * </pre>
 * 
 * 
 */
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "PersonType", propOrder = {
    "mailingAddress",
    "residentialAddress"
})
public class PersonType {

    @XmlElement(name = "MailingAddress", required = true)
    protected AddressType mailingAddress;
    @XmlElement(name = "ResidentialAddress")
    protected AddressType residentialAddress;
    @XmlAttribute(name = "firstName")
    protected String firstName;
    @XmlAttribute(name = "lastName")
    protected String lastName;

    /**
     * Gets the value of the mailingAddress property.
     * 
     * @return
     *     possible object is
     *     {@link AddressType }
     *     
     */
    public AddressType getMailingAddress() {
        return mailingAddress;
    }

    /**
     * Sets the value of the mailingAddress property.
     * 
     * @param value
     *     allowed object is
     *     {@link AddressType }
     *     
     */
    public void setMailingAddress(AddressType value) {
        this.mailingAddress = value;
    }

    /**
     * Gets the value of the residentialAddress property.
     * 
     * @return
     *     possible object is
     *     {@link AddressType }
     *     
     */
    public AddressType getResidentialAddress() {
        return residentialAddress;
    }

    /**
     * Sets the value of the residentialAddress property.
     * 
     * @param value
     *     allowed object is
     *     {@link AddressType }
     *     
     */
    public void setResidentialAddress(AddressType value) {
        this.residentialAddress = value;
    }

    /**
     * Gets the value of the firstName property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getFirstName() {
        return firstName;
    }

    /**
     * Sets the value of the firstName property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setFirstName(String value) {
        this.firstName = value;
    }

    /**
     * Gets the value of the lastName property.
     * 
     * @return
     *     possible object is
     *     {@link String }
     *     
     */
    public String getLastName() {
        return lastName;
    }

    /**
     * Sets the value of the lastName property.
     * 
     * @param value
     *     allowed object is
     *     {@link String }
     *     
     */
    public void setLastName(String value) {
        this.lastName = value;
    }

}

JAXB-generated AddressType.java

1 2 3 Page 1
Page 1 of 3