Vous êtes sur la page 1sur 27

HyperJAXB - Relational persistence for

JAXB
Aleksei Valikov

1. Introduction
Primary goal of this development is to provide JAXB objects with a relational persistence layer. To achieve this
goal, we have developed an add-on that allows combining Sun's reference implementation of JAXB (XJC) with
Hibernate, a well-known object/relational mapping tool.

To map between relational tables and columns and Java objects, Hibernate requires an explicit definition of
mapping, usually in a form of XML file. Having this mapping, Hibernate can save objects in a relational data-
base, load them back, query for them etc. Consequently, to persist JAXB objects with Hibernate, we need to
produce object/relational mapping for these objects.

Instead of defining mappings in XML files, many Hibernate users prefer using XDoclet technology that allows
to specify mapping properties directly in the source code of the object classes. In this case, source code is an-
notated with special tags called "xdoclets". XDoclet parses source code and generates required artifacts (like
Hibernate mapping). XDoclet allows developers to remain source-centric instead of supporting several related
but separated artifacts (source code and mapping definitions) in parallel.

XDoclet is a perfect point to bring JAXB and Hibernate together. On the one hand, JAXB implementations
automatically generate source code of Java objects based on the XML Schema. On the other hand, XDoclet
automatically generates object/relational mapping for Hibernate based on the annotated source code. The only
missing fragment in this puzzle is automatic generation of xdoclets in JAXB objects. This missing fragment is
implemented by the HyperJAXB package.

HyperJAXB extends the code generated by Sun's reference implementation of JAXB with Hibernate xdoclet
annotations. Annotated sources of JAXB objects are processed into object/relational mapping for Hibernate.
Hibernate also provides tools to produce database schema for the target database from the generated mapping.

A combination of JAXB RI, HyperJAXB and Hibernate tools allows automatically generating the following ar-
tifacts:

• source code of JAXB objects with Hibernate XDoclet annotations;

• object/relational mapping for Hibernate;

• database schema for the target database.

2. Generating Hibernate xdoclets and other required con-


structs

2.1. Basics

In order to make JAXB objects persistable, we need to generate adequate object/relational mapping annotations

1
HyperJAXB - Relational persistence for JAXB

for all the stateful elements of the classes. This effectively means that we have to map all the properties of the
generated implementation classes.

Depending on the cardinality, properties may be single or collection properties. Collection properties are those
returning instances of java.util.Set, java.util.Map, java.util.List and so on.

Collections may be homogeneous (all elements have the same type) and heterogeneous (types of elements may
differ). Although both options are used in JAXB, Hibernate generally discourages usage of heterogeneous col-
lections. Therefore we introduce additional constructions to homogenize heterogeneous collections.

Collections or single properties may store simple or complex values. HyperJAXB supports five basic categories
of values:

• simple values - primitives (int, float, boolean and so on) and basic object types (java.lang.String,
java.lang.Boolean, java.math.BigDecimal etc.);

• persistent typesafe enums;

• "any" type;

• DOM elements;

• complex values - references to other composite objects.

The task of the add-on is to generate mapping definitions for properties of all of the types as well as other con-
structs which are required for the mapping to work.

2.2. General constructs

2.2.1. Class-level xdoclets

Hibernate requires persistent classes to be explicitly declared. To achieve this, generated implementation
classes are annotated with @hibernate.class xdoclet. Name of the database table is explicitly specified in the
table attribute of this xdoclet.

Table name is derived from the name of the implemented interface. Derivation algorithm ensures that generated
name is unique across all classes of all packages. In future we plan to make name of the table customizable.

Since different packages may contain classes with equal names, auto-import must be turned off to avoid name
collission. This is achieved with auto-import="false" in @hibernate.mapping class-level xdoclet.

// ...
package de.baw.nokis.impl;

/**
* @hibernate.mapping auto-import="false"
* @hibernate.class table="RespParty"
*/
public class RespPartyImpl implements de.baw.nokis.RespParty // ...
{
// ...
}

XJC generates implementation classes for top-level elements as subclasses of implementation classes of their
complex types. HyperJAXB follows "table-per-class" strategy in polymorphic mapping. Accordingly, imple-
mentation classes for top level elements are annotated with @hibernate.joined-subclass and

2
HyperJAXB - Relational persistence for JAXB

@hibernate.joined-subclass-key xdoclets. Joined subclass key column is always named parentid.

// ...
package de.baw.nokis.impl;

/**
* @hibernate.joined-subclass table="Party"
* @hibernate.joined-subclass-key column="parentid"
*/
public class PartyImpl extends de.baw.nokis.impl.RespPartyImpl
implements de.baw.nokis.Party // ...
{
// ...
}

2.2.1.1. Customization
You may customize @hibernate.class and @hibernate.joined-subclass xdoclets by providing your own
annotations in the binding customization. Consider the following example:

<xs:element name="metadata" type="iso19115:MD_Metadata">


<xs:annotation>
<xs:appinfo>
<jaxb:class>
<jaxb:javadoc>
@hyperjaxb.hibernate.joined-subclass table="MetadataElement"
</jaxb:javadoc>
</jaxb:class>
</xs:appinfo>
</xs:annotation>
</xs:element>

<xs:complexType name="MD_Metadata">
<xs:annotation>
<xs:appinfo>
<jaxb:class>
<jaxb:javadoc>
@hyperjaxb.hibernate.class table="Metadata"
</jaxb:javadoc>
</jaxb:class>
</xs:appinfo>
</xs:annotation>
<!-- ... -->
</xs:complexType>

These customization will map metadata element onto the MetadataElement table and MD_Metadata complex
type onto the Metadata table.

/**
* @hibernate.mapping auto-import="false"
* @hibernate.class table="Metadata"
*/
public class MDMetadataImpl implements MDMetadata // ...
{ // ... }

/**
* @hibernate.joined-subclass table="MetadataElement"
* @hibernate.joined-subclass-key column="parentid"
*/
public class MetadataImpl extends MDMetadataImpl // ...
{ // ... }

This trick is especially helpful, if your classes have names prohibited in your database (like Table or User).

Please note that xdoclets you provide in customizing annotations replace xdoclets generated by HyperJAXB by
default completely.

3
HyperJAXB - Relational persistence for JAXB

2.2.1.2. Customizing the cache usage


You may also customize usage of the Hibernate cache (from simple test):

<xsd:complexType name="EntryType">
<xsd:annotation>
<xsd:appinfo>
<jaxb:class>
<jaxb:javadoc>
@hyperjaxb.hibernate.cache usage="read-write"
</jaxb:javadoc>
</jaxb:class>
</xsd:appinfo>
</xsd:annotation>
<!-- ... -->
</xsd:complexType>

2.2.2. Identifier property

Hibernate requires that each persistent class declares a primary key column of the database table. To achieve
this, each implementing class (except for the subclasses) is extended with an identifier property. This property
is defined by the string idInternal field and a getter/setter pair getIdInternal()/setIdInternal(String an-
Id). Identifier is a 32-character string assigned by the Hibernate when object is saved into the database for the
first time. To allow Hibernate distinguish new instances, unsaved-value="null" attribute is generated as well.

// ...
package de.baw.nokis.impl;

// ..
public class ContactImpl implements de.baw.nokis.Contact // ...
{
private java.lang.String idInternal;

/**
* @hibernate.id type="string" unsaved-value="null" length="32" generator-class="uuid.hex"
*/
public java.lang.String getIdInternal()
{
return idinternal;
}

public void setIdInternal(java.lang.String anId)


{
idInternal = anId;
}

// ...
}

2.2.2.1. Customizing the identifier property


Identifier property is customizable. Instead of generating a new idInternal property, you may force Hyper-
JAXB to use an existing property as identifier. To do this, include @hyperjaxb.hibernate.id into the property
javadoc annotation (from the simple test):

<xsd:complexType name="EntryType">
<xsd:sequence><!-- ... --></xsd:sequence>
<xsd:attribute name="id" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<jaxb:property>
<jaxb:javadoc>
@hyperjaxb.hibernate.id unsaved-value="null" generator-class="uuid.hex"

4
HyperJAXB - Relational persistence for JAXB

</jaxb:javadoc>
</jaxb:property>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
</xsd:complexType>

If you use an id generator that needs to be parametrized, include the required parameters in
@hyperjaxb.hibernate.generator-param.

2.3. Mapping properties

2.3.1. Single properties

2.3.1.1. Simple properties


Simple properties of JAXB objects are annotated with @hibernate.property xdoclet:

/**
* @hibernate.property
*/
public java.lang.String getPostCode()
{
return _PostCode;
}

2.3.1.1.1. Customizing simple single properties

To customize simple single property, include a @hyperjaxb.hibernate.property xdoclet into the property
javadoc (from the simple test):

<xsd:complexType name="EntryType">
<!-- ... -->
<xsd:sequence>
<!-- ... -->
<xsd:element name="value" type="xsd:string">
<xsd:annotation>
<xsd:appinfo>
<jaxb:property>
<jaxb:javadoc>
@hyperjaxb.hibernate.property length="12"
</jaxb:javadoc>
</jaxb:property>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<!-- ... -->
</xsd:sequence>
<!-- ... -->
</xsd:complexType>

2.3.1.2. Simple binary properties


There is a special case of binary simple properties: for XML Schema types like base64Binary JAXB generates
byte[]-typed properties. To support persistance of binary properties, HyperJAXB uses Hibernate's
net.sf.hibernate.type.BinaryType.

/**
* @hibernate.property type="net.sf.hibernate.type.BinaryType"
*/
public byte[] getValue()

5
HyperJAXB - Relational persistence for JAXB

{
return _Value;
}

2.3.1.3. Enum properties


As an extension, JAXB supports typesafe enum model for the enumerated simple types. Consider the following
schema fragment:

<xsd:annotation>
<xsd:appinfo>
<jaxb:globalBindings typesafeEnumBase="test:SexType"/>
</xsd:appinfo>
</xsd:annotation>

<xsd:simpleType name="SexType">
<xsd:restriction base="xsd:string">
<xsd:enumeration value="Male"/>
<xsd:enumeration value="Female"/>
</xsd:restriction>
</xsd:simpleType>

For the enumerated simple type SexType from the fragment above JAXB generates the following class:

// ...
package test;

/**
* Java content class for SexType.
* <p>The following schema fragment specifies the expected content contained within this java content
* <p>
* <pre>
* &lt;restriction base="{http://www.w3.org/2001/XMLSchema}string">
* &lt;enumeration value="Male"/>
* &lt;enumeration value="Female"/>
* &lt;/restriction>
* </pre>
*/
public class SexType implements java.io.Serializable
{
private final static java.util.Map valueMap = new java.util.HashMap();
public final static java.lang.String _MALE = com.sun.xml.bind.DatatypeConverterImpl.installHook("M
public final static test.SexType MALE = new test.SexType(_MALE);
public final static java.lang.String _FEMALE = com.sun.xml.bind.DatatypeConverterImpl.installHook(
public final static test.SexType FEMALE = new test.SexType(_FEMALE);
private final java.lang.String lexicalValue;
private final java.lang.String value;

protected SexType(java.lang.String v) {
value = v;
lexicalValue = v;
valueMap.put(v, this);
}

public java.lang.String toString() {


return lexicalValue;
}

public java.lang.String getValue() {


return value;
}

public final int hashCode() {


return super.hashCode();
}

public final boolean equals(java.lang.Object o) {

6
HyperJAXB - Relational persistence for JAXB

return super.equals(o);
}

public static test.SexType fromValue(java.lang.String value) {


test.SexType t = ((test.SexType) valueMap.get(value));
if (t == null) {
throw new java.lang.IllegalArgumentException();
} else {
return t;
}
}

public static test.SexType fromString(java.lang.String str) {


return fromValue(str);
}
}

HyperJAXB supports persistance of typesafe enums with the help of user types (see
net.sf.hibernate.UserType). Each of the typesafe enum classes is appended with an inner static class Type:

// ...

package test;

// ...

public class SexType implements java.io.Serializable


{
// ...
public static class Type implements net.sf.hibernate.UserType, java.io.Serializable
{
public int[] sqlTypes() {
return new int[] {java.sql.Types.VARCHAR };
}

public java.lang.Class returnedClass() {


return test.SexType.class;
}

public boolean equals(java.lang.Object x, java.lang.Object y) {


return (x == y);
}

public java.lang.Object deepCopy(java.lang.Object value) {


return value;
}

public boolean isMutable() {


return false;
}

public java.lang.Object nullSafeGet(java.sql.ResultSet resultSet, java.lang.String[] names, ja


throws java.sql.SQLException, net.sf.hibernate.HibernateException
{
java.lang.String name = resultSet.getString(names[ 0 ]);
return (resultSet.wasNull()?null:test.SexType.fromString(name));
}

public void nullSafeSet(java.sql.PreparedStatement statement, java.lang.Object value, int inde


throws java.sql.SQLException, net.sf.hibernate.HibernateException
{
if (value == null) {
statement.setNull(index, java.sql.Types.VARCHAR);
} else {
statement.setString(index, value.toString());
}
}
}
}

7
HyperJAXB - Relational persistence for JAXB

Generated user type class is specified as the type of the property:

/**
* @hibernate.property type="test.SexType$Type"
*/
public test.SexType getSex()
{
return _Sex;
}

2.3.1.4. DOM properties


As a vendor extension, XJC supports xjc:dom customization, which allows you to map a certain parts of the
schema into a DOM tree. This is very helpful when you deal with semi-structured content or large external
schemas that you don't want to process structurally.

For xjc:dom-annotated constructs, XJC generates properties with types from DOM hierarchy.

HyperJAXB supports persistance of DOM properties with the help of a user type
de.fzi.dbs.jaxb.addon.hibernate.ElementType. This special user type serializes DOM content when sav-
ing and parses the XML back when loading data from the database.

/**
* @hibernate.property type="de.fzi.dbs.jaxb.addon.hibernate.ElementType"
*/
public org.w3c.dom.Element getMath()
{
return _Math;
}

2.3.1.5. Properties of "any" type


The type "any" is a very powerful XML Schema construct. It adds great flexibility to XML Schemas. On the
object level, this construct is represented by the heterogeneous association. Although this type of association is
not directly supported by the relational model, Hibernate still allows to map it using any mapping.

Consider the following schema fragment:

<xsd:element name="root" type="test:RootType"/>

<xsd:complexType name="RootType">
<xsd:sequence>
<xsd:any processContents="strict"/>
</xsd:sequence>
</xsd:complexType>

The element root may contain any other declared element. To map "any" construct, the corresponding property
is annotated with @hibernate.any and @hibernate.any-column xdoclets:

/**
* @hibernate.any id-type="string" cascade="all"
* @hibernate.any-column name="Any_class"
* @hibernate.any-column name="Any_id" length="32"
*/
public java.lang.Object getAny()
{
return _Any;
}

2.3.1.6. Complex properties

8
HyperJAXB - Relational persistence for JAXB

Complex properties are essentially one-to-one associations which may be implemented in Hibernate with
primary key or foreign key constraints. Primary key associations require related objects to have equal identifi-
ers. This does not suit well our identifier model (each object has its own globally unique identifier). Therefore,
one-to-one associations are implemented with foreign keys. This corresponds to the many-to-one mapping:

/**
* @hibernate.many-to-one cascade="all"
* class="de.baw.nokis.impl.RespPartyRecImpl"
*/
public de.baw.nokis.RespPartyRec getRespPartyRec()
{
return _RespPartyRec;
}

2.3.2. Collection properties

2.3.2.1. Proxies for collection properties


During the early implementation phases of HyperJAXB we have found out that JAXB collection properties
cannot be persisted with Hibernate directly. This is due to a number of reasons. First of all, Hibernate requires
all persistent properties (including persistent collection properties) to have setters, while JAXB generates no
setters for collections. Second, XJC exposes collections wrapped in proxy classes:

protected com.sun.xml.bind.util.ListImpl _Value =


new com.sun.xml.bind.util.ListImpl(new ArrayList());

public java.util.List getValue()


{
return _Value;
}

At the same time Hibernate checks identity of collection properties by object identity. Proxied field makes Hi-
bernate think that a new collection was assigned to the property. This results in full rewriting of the collection
when object is saved or loaded.

To solve this and few other problems, we generate a supporting inner property for each of the collection proper-
ties. Main property is exposed as proxy of the inner property. Inner property has both getter and setter accessing
the field directly and may be persisted by Hibernate. For example, the value collection property in the listing
above will be augmented as follows:

protected com.sun.xml.bind.util.ListImpl _Value =


new com.sun.xml.bind.util.ListImpl(new test.impl.ElementTypeImpl.ValueInternalProxyList());

private java.util.List _ValueInternal = new java.util.ArrayList();

public java.util.List getValue()


{
return _Value;
}

public java.util.List getValueInternal()


{
return _ValueInternal;
}

public void setValueInternal(java.util.List theValueInternal)


{
_ValueInternal = theValueInternal;
}

public class ValueInternalProxyList extends java.util.AbstractList


{

9
HyperJAXB - Relational persistence for JAXB

public java.lang.Object get(int index)


{
return _ValueInternal.get(index);
}

public java.lang.Object set(int index, java.lang.Object o)


{
return _ValueInternal.set(index, o);
}

public void add(int index, java.lang.Object o)


{
_ValueInternal.add(index, o);
}

public java.lang.Object remove(int index)


{
return _ValueInternal.remove(index);
}

public int size()


{
return _ValueInternal.size();
}
}

In the code above, _ValueInternal is the real field that will hold the real collection object. Accessors get-
ValueInternal() and setValueInternal(...) provide direct access to this field and may be used by Hibern-
ate to persist the collection. The field _Value holds a JAXB proxy of the inner class ValueInternalProxyList,
which provides access to the _ValueInternal field.

Supporting inner properties are also used to homogenize heterogeneous collections (this usage is discussed
later).

2.3.2.2. Simple collections


Simple collections are implemented by Hibernate's "collections of values", which are mapped as follows:

/**
* @hibernate.list table="ContactImpl_FaxNumInternal" cascade="all" where="FaxNumInternal_index is not
* @hibernate.collection-key column="ContactImpl_id"
* @hibernate.collection-index column="FaxNumInternal_index"
* @hibernate.collection-element column="value" type="java.lang.String"
*/
public java.util.List getFaxNumInternal()
{
return _FaxNumInternal;
}

Collection key column is given the name classname_id, index column is propertyname_index, value column
is always named value. Note also where="propertyname_index is not null" condition which allows distin-
guishing values belonging to different collections properties.

Binary collection implementations is currently under development.

2.3.2.3. Collection of typesafe enums


Collections of typesafe enums are mapped similarly to simple collections except for their type, which is set to
the generated user type.

/**
* @hibernate.list table="RootType_SexesInternal" cascade="all" where="SexesInternal_index is not null

10
HyperJAXB - Relational persistence for JAXB

* @hibernate.collection-key column="RootTypeImpl_id"
* @hibernate.collection-index column="SexesInternal_index"
* @hibernate.collection-element column="value" type="test.SexType$Type"
*/
public java.util.List getSexesInternal()
{
return _SexesInternal;
}

2.3.2.4. Collection of DOM elements


Collections of DOM elements are mapped just like simple collections with the exception that element type is
set to de.fzi.dbs.jaxb.addon.hibernate.ElementType.

XML Schema fragment:

<xsd:complexType name="RootType">
<xsd:sequence>
<!-- ... -->
<xsd:element name="contents" maxOccurs="unbounded">
<xsd:annotation>
<xsd:appinfo>
<xjc:dom/>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
</xsd:sequence>
</xsd:complexType>

Augmented code:

/**
* @hibernate.list table="RootType_ContentsInternal" cascade="all" where="ContentsInternal_index is no
* @hibernate.collection-key column="RootTypeImpl_id"
* @hibernate.collection-index column="ContentsInternal_index"
* @hibernate.collection-element column="value" type="de.fzi.dbs.jaxb.addon.hibernate.ElementType"
*/
public java.util.List getContentsInternal()
{
return _ContentsInternal;
}

2.3.2.5. Collection of "any" constructs


Collection of "any" constructs is mapped using many-to-any.

XML Schema fragment:

<xsd:complexType name="GroupType">
<xsd:complexContent>
<xsd:extension base="test:Principal">
<xsd:sequence>
<xsd:any maxOccurs="unbounded" processContents="strict"/>
</xsd:sequence>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>

Augmented code:

/**
* @hibernate.list table="GroupType_AnyInternal" cascade="all" where="AnyInternal_index is not null"
* @hibernate.collection-key column="GroupTypeImpl_id"

11
HyperJAXB - Relational persistence for JAXB

* @hibernate.collection-index column="AnyInternal_index"
* @hibernate.many-to-any id-type="string"
* @hibernate.many-to-any-column name="AnyInternal_class"
* @hibernate.many-to-any-column name="AnyInternal_id" length="32"
*/
public java.util.List getAnyInternal()
{
return _AnyInternal;
}

2.3.2.6. Complex collections


Complex collections are implemented by one-to-many mappings.

/**
* @hibernate.list cascade="all" where="CntOnlineResInternal_index is not null"
* @hibernate.collection-key column="ContactImpl_id"
* @hibernate.collection-index column="CntOnlineResInternal_index"
* @hibernate.collection-one-to-many class="de.baw.nokis.impl.OnLineResImpl"
*/
public java.util.List getCntOnlineResInternal()
{
return _CntOnlineResInternal;
}

Collection key column is given the name classname_id, index column is named propertyname_index.

2.3.2.7. Heterogeneous collections


Heterogeneous collections are collections holding more than one type of elements. In JAXB, these collections
are produced for repeatable sequences or choices. Consider the following schema type:

<xsd:complexType name="RootType">
<xsd:sequence maxOccurs="unbounded">
<xsd:element name="element1" type="test:ElementType"/>
<xsd:element name="element2" type="test:ElementType"/>
</xsd:sequence>
</xsd:complexType>

This type will be represented by the following interface:

public interface RootType {


/**
* Objects of the following type(s) are allowed in the list
* {@link RootType.Element1}
* {@link RootType.Element2}
*/
java.util.List getElement1AndElement2();

public interface Element1 extends javax.xml.bind.Element, ElementType {}


public interface Element2 extends javax.xml.bind.Element, ElementType {}
}

The getter getElement1AndElement2() exposes a heterogeneous collection. Although Hibernate can map het-
erogeneous collection properties like this one using many-to-any mapping, this approach is not recommended.
However, heterogeneous collections may be homogenized by introducing a new aggregating type:

public interface Element1AndElement2 {

public Element2 getElement2();


public void setElement2(Element2 theElement2);
public Element1 getElement1();
public void setElement1(Element1 theElement1);

12
HyperJAXB - Relational persistence for JAXB

This type has one property per type allowed in the heterogeneous collection. It is obvious that for any hetero-
geneous collection storing Element1 or Element2 instances there is an equivalent homogeneous collection stor-
ing instances of Element1AndElement2. This approach is used in HyperJAXB to generate supporting inner
properties as homogeneous collections. Inner collection is automatically converted into a heterogeneous collec-
tion when it is exposed by the proxy list class.

2.3.2.8. Customizing collections


Mapping of the collection properties can be customized. To do this, include a @hyperjaxb.hibernate.list
xdoclet into the corresponding property javadoc annotation. If this xdoclet is found, HyperJAXB will generate
no collection property mappings by its own. Instead, HyperJAXB will copy all the corresponding xdoclets from
the annotation. To be more specific, these xdoclets are:

• @hyperjaxb.hibernate.list

• @hyperjaxb.hibernate.collection-cache

• @hyperjaxb.hibernate.collection-jcs-cache

• @hyperjaxb.hibernate.collection-key

• @hyperjaxb.hibernate.collection-key-column

• @hyperjaxb.hibernate.collection-index

• @hyperjaxb.hibernate.collection-element

• @hyperjaxb.hibernate.collection-composite-element

• @hyperjaxb.hibernate.collection-one-to-many

• @hyperjaxb.hibernate.index-many-to-many

• @hyperjaxb.hibernate.many-to-many

• @hyperjaxb.hibernate.many-to-any

• @hyperjaxb.hibernate.many-to-any-column

Please note that you will need to take care of table names, class names, element names etc. by yourself.

As an example, we will demonstrate customization of the Address collection withing User complex type:

<xs:complexType name="User">
<xs:sequence>
<xs:element name="Address" type="pp:Address" maxOccurs="unbounded">
<xs:annotation>
<xs:appinfo>
<jaxb:property>
<jaxb:javadoc>
@hyperjaxb.hibernate.list
table="address" cascade="all" where="address_id is not null"
@hyperjaxb.hibernate.collection-key
column="user_id"
@hyperjaxb.hibernate.collection-index
column="address_id"

13
HyperJAXB - Relational persistence for JAXB

@hyperjaxb.hibernate.collection-one-to-many
cascade="all"
class="com.pps.schema.impl.AddressImpl"
</jaxb:javadoc>
</jaxb:property>
</xs:appinfo>
</xs:annotation>
</xs:element>
<!-- ... -->
</xs:sequence>
<!-- ... -->
</xs:complexType>

This customization results in the following comment on the getAddressInternal() method:

/**
* @hibernate.list table="address" where="address_id is not null" cascade="all"
* @hibernate.collection-key column="user_id"
* @hibernate.collection-index column="address_id"
* @hibernate.collection-one-to-many class="com.pps.schema.impl.AddressImpl" cascade="all"
*/
public java.util.List getAddressInternal() {
return _AddressInternal;
}

And, finally, the Hibernate mapping of this collection is as follows:

<list
name="addressInternal"
table="address"
lazy="false"
inverse="false"
cascade="all"
where="address_id is not null">

<key column="user_id"/>

<index column="address_id"/>

<one-to-many class="com.pps.schema.impl.AddressImpl"/>

</list>

2.3.3. Ignoring properties

You may instruct HyperJAXB to ignore the certain property. To do this, mark this property with
@hyperjaxb.ignore (from simple test):

<xsd:complexType name="ElementType">
<xsd:sequence>
<!-- ... -->
<xsd:element name="ignored" type="xsd:string" minOccurs="0">
<xsd:annotation>
<xsd:appinfo>
<jaxb:property>
<jaxb:javadoc>
@hyperjaxb.ignore
</jaxb:javadoc>
</jaxb:property>
</xsd:appinfo>
</xsd:annotation>
</xsd:element>
<!-- ... -->
</xsd:sequence>
</xsd:complexType>

14
HyperJAXB - Relational persistence for JAXB

HyperJAXB will generate no xdoclets for the ignored properties. Use this customization if you don't want a cer-
tain property to be persisted.

3. Using HyperJAXB with XJC


This section describes the following aspects of using HyperJAXB:

• generating annotated JAXB classes;

• building generated classes;

• generating the Hibernate mapping;

• generating the database schema.

3.1. Using XJC and HyperJAXB to generate annotated class

To use HyperJAXB with XJC you need to include HyperJAXB library (hyperjaxb.jar) into the class path and
turn on the add-on. Add-on is turned on using arg elements. A sample build file fragment is presented below:

<target name="generate.sources">
<taskdef name="xjc"
classname="com.sun.tools.xjc.XJCTask"
classpathref="xjc.lib.path"/>
<mkdir dir="${generated.sources}"/>
<xjc target="${generated.sources}">
<arg line="-nv"/>
<arg line="-extension"/>
<arg line="-Xhibernate-xdoclets"/>
<binding dir="${basedir}">
<include name="binding/*.xml"/>
</binding>
<schema dir="${basedir}">
<include name="schema/*.xsd"/>
</schema>
</xjc>
</target>

It is assumed that xjc.lib.path contains all the libraries required by XJC as well as hyperjaxb.jar and hi-
bernate2.jar.

After this step the generated.sources folder should contain generated sources annotated with Hibernate
xdoclets.

3.2. Building generated classes

Building generated classes is not much different from building normal Java classes. However, sometimes it
makes sense to keep generated sources separate. If this is the case you'll need to build both generated and
manually written sources together using a target like:

<target name="compile" depends="generate.sources">


<mkdir dir="${classes}"/>
<javac destdir="${classes}" debug="true"
srcdir="${generated.sources}"
classpathref="compile.lib.path">
</javac>
<copy todir="${classes}">

15
HyperJAXB - Relational persistence for JAXB

<fileset dir="${generated.sources}">
<exclude name="**/*.java"/>
</fileset>
</copy>
</target>

<target name="jar" depends="compile">


<mkdir dir="${test.lib.dir}"/>
<jar jarfile="${test.lib.dir}/${jar.name}.jar" basedir="${classes}"/>
</target>

In this target, compile.lib.path must include XJC libraries (HyperJAXB library is not required). We also
need to copy resources with the copy task.

3.3. Generating the Hibernate mapping

Hibernate mapping is generated using the Hibernate XDoclet module. You'll first need to define the task and
then use it to produce the mapping. Here is the sample ant target:

<target name="generate.hibernate.mapping" depends="jar">

<taskdef name="hibernatedoclet"
classname="xdoclet.modules.hibernate.HibernateDocletTask"
classpathref="hibernatedoclet.lib.path"/>

<tstamp>
<format property="TODAY" pattern="dd-MM-yy"/>
</tstamp>

<mkdir dir="${hibernate.mapping}"/>

<hibernatedoclet
destdir="${hibernate.mapping}"
mergedir="${generated.sources}"
excludedtags="@version,@author,@todo,@see,@throws"
addedtags="@xdoclet-generated at ${TODAY},@copyright The XDoclet Team,@author XDoclet"
force="false"
verbose="false">

<fileset dir="${generated.sources}">
<include name="**/*.java"/>
<exclude name="**/runtime/*.java"/>
</fileset>

<hibernate version="2.0"/>
</hibernatedoclet>
</target>

The path hibernatedoclet.lib.path in this target must include the whole set of Hibernate XDoclet libraries.
Please consult the Hibernate documentation for a detailed listing.

After this step you should receive a number of Hibernate mapping files (???.hbm.xml) in your hibernate dir-
ectory.

3.4. Generating the database schema

After the Hibernate mapping is generated it may be used to produce a database schema for the target database.

<target name="export.database.schema" depends="generate.hibernate.mapping">

<taskdef name="schemaexport"
classname="net.sf.hibernate.tool.hbm2ddl.SchemaExportTask"

16
HyperJAXB - Relational persistence for JAXB

classpathref="schemaexport.lib.path"/>

<mkdir dir="${database}"/>

<schemaexport
properties="${hibernate.properties}"
quiet="yes"
text="no"
drop="no"
delimiter=";"
output="${database}/schema.sql">
<fileset dir="${hibernate.mapping}">
<include name="**/*.hbm.xml"/>
</fileset>
</schemaexport>

</target>

This target requires Hibernate properties defined in hibernate.properties file and ???.hbm.xml mapping
files in hibernate directory. Output is the database schema in database/schema.sql. If text attribute is set to
no (not text only), this task will also initialize the database defined in Hibernate properties.

4. Persisting JAXB objects with Hibernate


In this section we will consider two target usage scenarios: saving an unmarshalled JAXB object into the data-
base and marshalling an object loaded from the database.

4.1. Prerequisites

4.1.1. Setting up a Hibernate session factory

Before you can use Hibernate to persist your objects, you'll need to set up a session factory. This basically
means that you'll need to load Hibernate properties and mapping files. The following code illustrates the factory
setup process.

/**
* Test setup.
*
* @throws Exception In case of setup problems.
*/
public void setUp() throws Exception
{
super.setUp();
final Configuration cfg = new Configuration();

final Properties properties = new Properties();


properties.load(new FileInputStream(getHibernatePropertiesFile()));
cfg.setProperties(properties);
addDirectory(cfg, getHibernateDirectory(), true, new DefaultFilenameFilter("*.hbm.xml"));

sessionFactory = cfg.buildSessionFactory();
}

private Configuration addDirectory(final Configuration configuration,


final File directory, final boolean recurse, final FilenameFilter filenameFilter)
throws IOException, MappingException
{
Configuration extendedConfiguration = configuration;
if (!directory.isDirectory())
{
throw new IOException("Passed file handle [" +
directory.getAbsolutePath() + "] is not a directory.");

17
HyperJAXB - Relational persistence for JAXB

}
final File[] files = directory.listFiles();
for (int index = 0; index < files.length; index++)
{
final File file = files[index];
if (recurse && file.isDirectory())
{
extendedConfiguration = addDirectory(extendedConfiguration,
file, recurse, filenameFilter);
}
else if (file.isFile() &&
filenameFilter.accept(directory, file.getName()))
{
extendedConfiguration = extendedConfiguration.addFile(file);
}
}
return configuration;
}

/**
* Returns the directory containing Hibernate mapping.
* @return Directory containing Hibernate mapping.
*/
public File getHibernateDirectory()
{
return new File("hibernate");
}

/**
* Returns Hibernate properties file.
* @return Hibernate properties file.
*/
public File getHibernatePropertiesFile()
{
return new File(getHibernateDirectory(), "hibernate.properties");
}

The code constructs a new factory configuration, loads properties from the hibernate.properties file and
adds all the mapping files (*.hbm.xml) recursively processing the hibernate directory and all its sub-
directories. The loaded configuration is then used to produce a Hibernate session factory.

4.1.2. Setting up JAXB context, marshaller, unmarshaller and validator

For the instances of unmarshaller, marshaller and validator used to work with JAXB objects we first need to
obtain a JAXB context:

final JAXBContext context = JAXBContext.newInstance("my.package.name");

unmarshaller = context.createUnmarshaller();
marshaller = context.createMarshaller();
validator = context.createValidator();

Instead of hardcoding the package name you could look it up dynamically using one of the generated classes:

final JAXBContext context = JAXBContext.newInstance(MyClass.class.getPackage().getName());

4.2. Unmarshall and saving

Having configured session factory and unmarshaller, you may now unmarshall the XML content into an object
and save this object into the database:

// Unmarshall the document


final RespParty respParty = (RespParty) unmarshaller.unmarshal(document);

18
HyperJAXB - Relational persistence for JAXB

// Open the session, save object into the database


final Session saveSession = sessionFactory.openSession();
// Save id for the later use
final String id = saveSession.save(respParty);
saveSession.flush();
// Close the session
saveSession.close()

The code above assumes document is a DOM document you want to unmarshall to receive an instance of a re-
sponsible party object (a RespParty instance).

4.3. Loading and marshalling

The loading and marshalling process is exactly the reverse of unmarshalling and saving. You look up the object
using its class and identifier (note that you'll need the implementation class, not the interface class to look up)
and utilize a marshaller to serialize it to DOM, SAX or any othe supported representation of XML.

// Open the session, load the object


final Session loadSession = sessionFactory.openSession();
final RespParty loadedRespParty = (RespParty) loadSession.load(RespPartyImpl.class, id);
loadSession.close();

// Marshall loaded object into the document


final Document loadedDocument = documentBuilder.newDocument();
marshaller.marshal(loadedRespParty, loadedDocument);

The document loadedDocument should be identical to the document that we've unmarshalled and saved in the
previous section (modulo whitespace).

5. Project template
The easiest way to setup your project with HyperJAXB, is to use the prepared project template, included in the
distribution (dist/template.zip). For a basic setup you will only need to extract the template.zip into a new
directory and place the schema into the schema folder.

The template project has the following directory structure:

• classes (generated during the build) - directory containing the compiled classes;

• database (generated during the build) - database directory;

• database/schema.sql (generated during the build) - database schema DDL;

• dist - distribution directory will contain the compiled and packed classes in a jar file;

• generated.source (generated during the build) - directory containing sources generated by JAXB and Hy-
perJAXB;

• hibernate - contains Hibernate files;

• hibernate/hibernate.properties - Hibernate properties;

• hibernate/mapping (generated during the build) - Hibernate mapping files (*.hbm.xml);

• lib - library directory (libraries are placed in their own subdirectories, ex. commons-lang.jar in lib/

19
HyperJAXB - Relational persistence for JAXB

jakarta-commons directory);

• log4j - Log4J directory;

• log4j/log4j.properties - Log4J configuration;

• schema - schema directory (the build assumes that schema files have *.xsd extension;

• src - non-generated source files.

To configure your HyperJAXB project, you'll need to edit the following files:

• build.xml

• text property - set to yes if you only want to generate database schema DDL without exporting it into
the database (default), specify no if you want to export the schema into the database;

• name property - specify the name of the project (default is template);

• compile.lib.path path - modify to add the required libraries;

• hibernate/hibernate.properties - set database properties used by Hibernate to generate mapping and


database schema;

• log4j/log4j.properties - configuration of logs used during the generation process.

Template build defines the following targets:

• init - initializes before the build (does noting in the template build);

• clean - cleans before the build (in the template build, deletes classes, generated.sources, hibernate/map-
ping and database directories);

• generate.sources - invokes XJC and HyperJAXB to generate annotated sources in the gener-
ated.sources directory;

• compile (depends on the generate.sources target) - compiles generated and static (those from the src dir-
ectory) sources into the classes directory;

• jar (depends on the compile target) - packs the compiled classes and places the generated jar file into the
dist directory;

• generate.hibernate.mapping (depends on the jar target) - generates Hibernate mapping in the hibern-
ate/mapping directory;

• export.database.schema (depends on the generate.hibernate.mapping target) - exports database


schema into the DDL file database/schema.sql and, if text property is set to no, also into the database
specified in the hibernate/hibernate.properties file;

• all (depends on the export.database.schema target) - full build process (default target).

6. Sample application

20
HyperJAXB - Relational persistence for JAXB

HyperJAXB distribution contains a number of test projects in the tests directory. In this section we will con-
sider one of these examples (iso19115) in detail.

The sample application we will examine demonstrates a full XML-objects-database roundtrip:

• XML document (initial document) is loaded from file and unmarshalled into a JAXB object;

• the object is saved into the database;

• the object is loaded from the database (in a new session);

• loaded object is marshalled into an XML document (final document);

• initial and final documents are compared for identity.

To support this roundtrip we use XJC and HyperJAXB to generate annotated JAXB classes. Generated sources
are then utilized to produce Hibernate mapping and database schema.

To build and run the application simply run runtest iso19115 in the tests directory. This will generate JAXB
objects out of the schema, produce Hibernate mapping, database schema and finally run the roundtrip test case.
You will not need to set up the database. Example runs against an embedded in-memory HSQL database that
does not require any setup.

6.1. The schema

The starting point is an XML schema stored in tests/iso19115/schema/schema.xsd. This schema is a very
small sample schema based on the ISO standard for geographic metadata.

21
HyperJAXB - Relational persistence for JAXB

6.2. Generated artifacts

During the build process, Ant generates the following artifacts:

• sources of JAXB objects in (with Hibernate xdoclets) generated.sources;

• compiled and packed classes of the projects in lib/test.jar;

• Hibernate mappings in the hibernate/mapping;

• database schema in the database/schema.sql.

The generated classes implement the following content interface structure:

22
HyperJAXB - Relational persistence for JAXB

The generated database schema has the following structure:

23
HyperJAXB - Relational persistence for JAXB

6.3. Roundtrip test case

The roundtrip test case (de.fzi.dbs.jaxb.tests.RoundtripTestCase) unmarshalls a sample XML document,


saves unmarshalled object into the database, loads it back and marshalls again into XML. What we need is to
test the identity of initial and final XML modulo whitespace. This is done with the following test method:

/**
* Tests document roundtrim: unmarshall into an object, save object into the database,
* load object from the database, marshall the object.
* Resulting XML should be equivalent to the initial XML.
*/
public void testRoundtrip()
{
try
{
// Unmarshall the document
final RespParty respParty = (RespParty) unmarshaller.unmarshal(document);
// Open the session, save object into the database
final Session saveSession = sessionFactory.openSession();
// Save id for the later use
final String id = (String) saveSession.save(respParty);
saveSession.flush();
// Close the session
saveSession.close();

// Open the session, load the object


final Session loadSession = sessionFactory.openSession();
final RespParty loadedRespParty = (RespParty) loadSession.load(RespPartyImpl.class, id);
loadSession.close();
// Marshall loaded object into the document
final Document loadedDocument = documentBuilder.newDocument();
marshaller.marshal(loadedRespParty, loadedDocument);

24
HyperJAXB - Relational persistence for JAXB

// Test that initial document and loaded document are identical


XMLAssert.assertXMLIdentical("Initial document and loaded document differ.",
new Diff(document, loadedDocument), true);
}
catch (Exception ex)
{
ex.printStackTrace();
Assert.fail("Unexpected exception is thrown.");
}
}

For testing, we use the following sample XML file:

<metadata xmlns="http://www.fzi.de/dbs/tests/iso19115">
<fileIdentifier>id000001</fileIdentifier>
<language>en</language>
<hierarchyLevel>dataset</hierarchyLevel>
<hierarchyLevel>series</hierarchyLevel>
<identificationInfo>
<abstract>The abstract.</abstract>
<purpose>The purpose.</purpose>
<status>planned</status>
<geographicBox>
<extentTypeCode>true</extentTypeCode>
<westBoundLongitude>11.7254223679</westBoundLongitude>
<eastBoundLongitude>11.8123425682</eastBoundLongitude>
<southBoundLatitude>48.3282639631</southBoundLatitude>
<northBoundLatitude>48.4438272635</northBoundLatitude>
</geographicBox>
</identificationInfo>
</metadata>

When we save object structure unmarshalled from this XML file, Hibernate executes the following SQL state-
ments (identifiers are shortened for better readability):

INSERT INTO EXGEOGRAPHICBOUNDINGBOX VALUES


('id-0003',true,48.4438272635,48.3282639631,11.8123425682,11.7254223679)
INSERT INTO MDIDENTIFICATION VALUES('id-0002','planned','The purpose.','id-0003','The abstract.')
INSERT INTO MDMETADATA VALUES('id-0001','id000001','en','id-0002')
INSERT INTO METADATA VALUES('id-0001')
INSERT INTO MDMETADATA_HIERARCHYLEVELINTERNAL VALUES('id-0001','dataset',0)
INSERT INTO MDMETADATA_HIERARCHYLEVELINTERNAL VALUES('id-0001','series',1)

Finally we have the following content in tables of our database:

Table 1. Contents of MDMetadata table

idInternal fileIdentifier language identificationInfo

id-0001 id000001 en id-0002

Table 2. Contents of Metadata table

parentid

id-0001

Table 3. Contents of MDMetadata_HierarchyLevelInternal table

25
HyperJAXB - Relational persistence for JAXB

MDMetadataImpl_id HierarchyLevelInternal_index value

id-0001 0 dataset

id-0001 1 series

Table 4. Contents of MDIdentification table

idInternal status purpose geographicBox abstract

id-0002 planned The purpose. id-0003 The abstract.

Table 5. Contents of EXGeographicBoundingBox table

idInternal extentTypeCode northBoundLat- southBoundLat- eastBoundLon- westBoundLon-


itude itude gitude gitude

id-0003 true 48.4438272635 48.3282639631 11.8123425682 11.7254223679

7. Acknowledgements
HyperJAXB and the sample application use or include the following products, libraries and tools:

• JAXB and XJC;


Java Architecture for XML Binding (JAXB) provides a convenient way to bind an XML schema to a rep-
resentation in Java code. You can find Jaxb at http://java.sun.com/xml/jaxb/.

• Hibernate;
Hibernate is a high performance object/relational persistence and query service for Java. You can find Hi-
bernate at http://www.hibernate.org

• Jakarta Commons libraries (Collections, Lang, Logging, BeanUtils, JXPath); The Jakarta Commons librar-
ies are dedicated to creating and maintaining reusable Java components. The libraries are:

• Collections - provides a suite of classes which extend the Java Collections Framework.

• Lang - This library provides extra funtionality for classes in java.lang.

• Logging - This library is a wrapper around a variety of logging API implementations.

• BeanUtils - provides easy-to-use wrappers around the Java reflection and introspection APIs.

• JXPath - provides utilities for manipulating Java classes that conform to the JavaBeans naming conven-
tions using the XPath syntax. The library also supports maps, DOM and other object models.
Jakarta Commons can be found at http://jakarta.apache.org

• DOM and SAX;


DOM (Document Object Model) is a language neutral programming interface for XML-processing. Access-
ing the XML is done via the object tree - rather than running through the document sequentially as done in
SAX (Simple API for XML). DOM is well suited for smaller documents since the access is more comfort-

26
HyperJAXB - Relational persistence for JAXB

able while SAX is better suited for bigger documents. While DOM can create XML documents SAX can
not. You can find DOM at http://www.w3.org/DOM/ and SAX at http://www.saxproject.org

• Dom4j;
Dom4j is a open source library for working with XML, XPath and XSLT for the Java platform using the
Java Collenctions Framework. It has full support for DOM, SAX and JAXP. You can find Dom4j at ht-
tp://www.dom4j.org/

• Xalan and Xerces;


Xalan and Xerces are both part of the Apache XML project ( http://xml.apache.org). Xalan is a XSLT
stylesheet processor and Xerces is a XML parser. Yet, Xerces can not only parse XML files but also gener-
ate them. Xalan for Java can be found at http://xml.apache.org/xalan-j/index.html, Xerces for Java at ht-
tp://xml.apache.org/xerces2-j/index.html.

• CGLib;
CGLib, the Java Code Generation Library, can be used to extend Java classes and implement Java inter-
faces at runtime. You can find CGLib at http://cglib.sourceforge.net

• HSQL database;
The Hypersonic SQL database is a Java database engine with a standard SQL and JDBC interface. Through
it's compactness, it is well suited for usage as a server in applets and applications. You can find the Hyper-
sonic SQL database at http://hsql.sourceforge.net

• Programmer's Friend components;


The Programmer's Friend class library contains a large collection of Java utility classes which can simplify
programming compared to just using the JDK. Programmer's Friend can be found at ht-
tp://www.programmers-friend.org/

• JUnit;
JUnit is a regression testing framework for developers who implement unit test cases. JUnit has a console
user interface as well as a GUI. You can find JUnit at: http://www.junit.org

• XML Unit;
XML Unit extends JUnit (and NUnit) to enable unit testing of XML. It can compare a control XML docu-
ment to a test document, validate documents and compare results of XPath expressions. XML Unit can be
found at http://xmlunit.sourceforge.net/

8. Legal
This product includes software developed by the Apache Software Foundation (http://www.apache.org/).

This product includes Hypersonic SQL.

27

Vous aimerez peut-être aussi