JPOX
JPOX
 Project  |  Ver 1.1  |  Ver 1.2  |  JDO  |  JPA  |  Guides
1.1 | Preparation | O/R Mapping | Runtime | Extensions | Developer
Persistence
Object Identity
Third Party Tools
XDoclet - JDO MetaData annotations

Java Persistent Objects JDO (JPOX) relies on the use of JDO Meta-Data for the classes to be persisted. The Meta-Data file(s) define how a class is persisted, from the basic information like which fields are persisted, down to whether the user wants to specify what column names are used in the database etc. Generating JDO Meta-Data files can be time consuming and laborious. Additionally if you swap JDO providers you will need to update your Meta-Data because there are a substantial number of vendor specific extensions available.

One way around the need to manually generate these files is to use XDoclet (JDODoclet) and add a form of annotations to your Java classes. This is available from the XDoclet project. Please note that XDoclet only recently implemented (some) support for JDO 2. It may be possible that your version only generates MetaData for JDO 1.0.1. Additionally, the JDO 2 support may not yet be complete in the version you use. Thus it is a good idea to check the output jdo metadata files for completeness.

XDoclet version 1.2.3 (or later) does contain some JDO 2.0 tags and so it should be possible to use XDoclet with JPOX 1.1 (JDO 2.0). Please consult the XDoclet mailing list for this of what is included.

The process of using XDoclet is straightforward. You add Javadoc tags to the various attributes in your class while you are writing it, and your run XDoclet over your classes which generates the Meta-Data files for you.

Disclaimer : JPOX does not provide support for nor endorse the use of XDoclet. We simply provide visibility of its existence and potential benefits for your organisation. You can obtain support for XDoclet usage from the XDoclet site.

Ant Task

To run xdoclet, you can use the following ant task:

<target name="jdodoclet" depends="init">
	<taskdef name="jdodoclet" classname="xdoclet.modules.jdo.JdoDocletTask">
		<classpath refid="project.class.path"/>
	</taskdef>

	<jdodoclet jdospec="2.0" destdir="${project.dir}/${ejbsrc.dir}">
		<fileset dir="../../src" defaultexcludes="yes"/>
		<jdometadata project="package" generation="package"/>
		<jdoobjectidgenerator/>
	</jdodoclet>
</target>

If you're using eclipse with lomboz, you can simply paste the above code fragment into your xdoclet.xml file and make the task "ejbdoclet" dependent on it.

To use JDO version 1 instead of 2, you must either use jdospec="1.0" or omit the jdospec attribute.

example class with datastore identity

To guide you through the tagging process, there is an example class below. It contains tags in the Javadoc headers for the class and for the attributes (fields). It should be self explanatory, when read in conjunction with the Meta-Data Guide.

package mydomain;

import ...;

/**
 * This is a sample class. In reality it represents a Supplier to a shop.
 *
 * @jdo.persistence-capable identity-type="datastore" table="MY_SUPPLIER"
 **/
public class Supplier
{
    /** Name of the Supplier
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="100"
     **/
    protected String name=null;

    /** Products for the supplier
     *
     * @jdo.field
     *    persistence-modifier="persistent"
     *    collection-type="collection"
     *    element-type="net.ajsoft.WebShop.Inventory.Product" 
     *    mapped-by="supplier"
     **/
    protected Collection products=new HashSet();

    /** Default constructor. */
    protected Supplier()
    {
    }

    ...
}
                

So as you see, the @jdo tags represent XML elements in the Meta-Data file. When the above class is passed through XDoclet, you will get a Meta-Data file containing the following

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE jdo PUBLIC 
 "-//Sun Microsystems, Inc.//DTD Java Data Objects Metadata 1.0//EN"
 "http://java.sun.com/dtd/jdo_1_0.dtd">

<jdo>
  <package name="net.ajsoft.WebShop.Inventory">
    <class name="Supplier" identity-type="datastore" table="MY_SUPPLIER">
      <field name="name" persistence-modifier="persistent">
        <column length="100"/>
      </field>

      <field name="products" persistence-modifier="persistent" mapped-by="supplier">
        <collection element-type="net.ajsoft.WebShop.Inventory.Product">
        </collection>
      </field>
    </class>
  </package>
</jdo>

So, as you see, the class-level "datastore" tag is placed within the class Meta-Data XML tag, and the "table" attribute is at class level. Similarly, the field-level tags are placed within the field Meta-Data XML tags. There are obviously many more tags that can be added, using the information in the Meta-Data Guide for JPOX, but this should be enough to give you an idea how XDoclet can aid you in deploying JDO solutions to JPOX.

In terms of XDoclet, you can find more extensive documentation on the XDoclet web site. Please be aware that the XDoclet documentation on that website is not necessarily correct - in the documentation for XDoclet 1.2 there are tags @sql.field, @sql.table listed. These tags are not used by XDoclet.

example class with application identity

To use application identity, the above class needs to be changed a bit:

/**
 * This is a sample class. In reality it represents a Supplier to a shop.
 * @author  Marco Schulze
 *
 * @jdo.persistence-capable
 *    identity-type="application"
 *    objectid-class = "SupplierID"
 *    table="MY_SUPPLIER"
 *
 * @jdo.create-objectid-class field-order="supplierGroupID, supplierID"
 **/
public class Supplier
{
    /** First part of the Supplier ID
     *
     * @jdo.field primary-key="true"
     * @jdo.column length="100"
     **/
    private String supplierGroupID;

    /** Second part of the Supplier ID
     *
     * @jdo.field primary-key="true"
     * @jdo.column length="100"
     **/
    private long supplierID;

    /** Name of the Supplier
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="100"
     **/
    protected String name=null;

    /** Products for the supplier
     *
     * @jdo.field
     *    persistence-modifier="persistent"
     *    collection-type="collection"
     *    element-type="net.ajsoft.WebShop.Inventory.Product" 
     *    mapped-by="supplier"
     **/
    protected Collection products=new HashSet();

    /** Default constructor. */
    protected Supplier()
    {
    }

    /** Constructor with primary key params */
    public Supplier(String supplierGroupID, long supplierID)
    {
      this.supplierGroupID = supplierGroupID;
      this.supplierID = supplierID;
    }

    ...
}

If you use application identity, you need to add field members for your primary key (which can be many fields to form a composite key). Additionally, you need to declare (and create) a separate class used as object id. Note, that it is a very good idea to use the full qualified class name (with package) in the tag as you might otherwise run into trouble when inheriting this jdo class.

Careful! When using application identity, you need to think about constraints of your database server: E.g. MySQL with InnoDB supports only a maximum length of 1024 bytes for a primary key. This is usually enough, if you limit strings to a length of 50 or 100, but it's good to keep it in mind.

XDoclet supports since 2005-08-29 the auto-generation of JDO object id classes. All that's needed is the tag '@jdo.create-objectid-class'. The attribute 'field-order' is optional.

If you create the object id manually, an example class may look like this:

public class SupplierID
{
    /**
     * All primary key members must be public fields with the same names
     * as in the persistence capable class.
     */
    public String supplierGroupID;
    public long supplierID;

    /**
     * There must be a default constructor.
     */
    public SupplierID() { }

    /**
     * There must be a constructor having a String parameter for accepting
     * the result of the toString() method.
     */
    public SupplierID(String keyStr)
    {
        String[] parts = keyStr.split("/");
        if (parts.length != 2)
        {
            throw new IllegalArgumentException("Invalid key string! It contains "+parts.length+" parts, but 2 are expected!");
        }
			
        this.supplierGroupID = parts[0];
        this.supplierID = Long.parseLong(parts[1]);
    }

    /**
     * We must implement toString() in a way that the one-string-constructor can create
     * an equal object id instance out of this result.
     */
    public String toString()
    {
        return supplierGroupID + '/' + Long.toString(supplierID);
    }

    /**
     * We must override the equals method.
     */
    public boolean equals(Object obj)
    {
        if (!(obj instanceof SupplierID))
            return false;

        SupplierID other = (SupplierID) obj;
        return this.supplierGroupID.equals(other.supplierGroupID) && this.supplierID == other.supplierID;
    }
    
    /**
     * We must override the hashCode() method.
     */
    public int hashCode()
    {
        return supplierGroupID.hashCode() ^ new Long(this.supplierID).hashCode();
    }
}
            	
map with normal relationship

The following code declares a relationship between two objects using a join table. You might read this document for better understanding.

    /**
     * @jdo.field
     *    persistence-modifier="persistent"
     *    collection-type="map"
     *    key-type="java.lang.String"
     *    value-type="Product"
     *    dependent="true"
     *
     * @jdo.join
     */
    private Map products = new HashMap();

The tag @jdo.join tells JPOX to create and use an extra join table. This allows you to use everything as key (the key is stored in the additional table).

The attribute dependent is optional. If you omit it, it defaults to false. It means that the objects in the map can only exist as long as they are map entries. If they are deleted out of the map or the object owning the map is deleted, they are removed from the datastore. Thus, you better think twice before using dependent="true" with a normal map.

map with inverse relationship

The following code declares a relationship between two objects without using a join table. The contained object knows it's container in this case. You might read this document for better understanding.

    /**
     * @jdo.field
     *    persistence-modifier="persistent"
     *    collection-type="map"
     *    key-type="java.lang.String"
     *    value-type="Product"
     *    dependent="true"
     *    mapped-by="supplier"
     *
     * @jdo.key mapped-by="productID"
     */
    private Map products = new HashMap();

The attribute mapped-by of @jdo.field defines the field in the contained object that points back to the container. Because we don't have an additional join table, this is the field by which JPOX knows to which container the contained object belongs.

Additionally, we need to define the tag @jdo.key. Because there is no join table, the value of the key field must be stored in the linked table and therefore be a member of the contained object. @jdo.key mapped-by declares which field of the contained object is used as map key.

collection with normal relationship

The declaration of a Collection (both List and Set) works quite similar to the map. Here an example for a normal 1-N-relationship:

    /**
     * @jdo.field
     *    persistence-modifier="persistent"
     *    collection-type="collection"
     *    element-type="Product"
     *    dependent="true"
     *
     * @jdo.join
     **/
    protected Set products = new HashSet();
              

The attribute dependent is optional and defaults to false. If it's true, the objects within this Set are deleted out of the datastore, when they are removed from this Set or when the Supplier object (the owner of this Set member) is deleted.

Note, that you could use protected Collection products as declaration which would behave the same way.

If the order is important, you must declare protected List products instead of using a Set - but the metadata is the same.

You might want to read additional documentation about 1-N-relationships for Lists and Sets.

collection with inverse relationship

An inverse 1-N-relationship looks quite similar as a normal one:

    /**
     * @jdo.field
     *    persistence-modifier="persistent"
     *    collection-type="collection"
     *    element-type="Product"
     *    dependent="true"
     *    mapped-by="supplier"
     **/
    protected Set products = new HashSet();
              

As you can see, only the tag @jdo.join is omitted and the attribute mapped-by added instead. Like with the inverse map, the mapped-by declares which member of Product points back to its owner Supplier.

inheritence of a persistent class

The following example shows how to extend our Supplier:

/**
 * @jdo.persistence-capable 
 *    identity-type="application"
 *    persistence-capable-superclass="Supplier"
 */
public class SpecialSupplier extends Supplier
{
  ...
}

It is a good idea to use the fully qualified class name for the attribute persistence-capable-superclass.

You should not declare an object-id class, because inherited persistence capable classes always use the same primary key as the mother.

fetch groups

As explained in this document, JDO 2.0 supports attach/detach with the detach process being controlled by fetch groups. The following code example shows how to declare fetch groups by using xdoclet:

/**
 * @jdo.persistence-capable 
 *    identity-type="application"
 *    persistence-capable-superclass="Supplier"
 * 
 * @jdo.fetch-group name="name" field-names="name"
 * @jdo.fetch-group name="address" post-load="true" field-names="address_street, address_city, address_country"
 * @jdo.fetch-group name="phone" field-names="phone_office, phone_mobile"
 * @jdo.fetch-group name="name-address" field-names="name" fetch-group-names="address"
 * @jdo.fetch-group name="name-phone" field-names="name" fetch-group-names="phone"
 * @jdo.fetch-group name="name-address-phone" field-names="name" fetch-group-names="address, phone"
 */
public class SpecialSupplier extends Supplier
{
  ...
    /** Name of the Supplier
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="100"
     **/
    private String name;

    /** Street of the Supplier
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="100"
     **/
    private String address_street;

    /** City of the Supplier
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="100"
     **/
    private String address_city;

    /** Country of the Supplier
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="100"
     **/
    private String address_country;
    
    /** Office phone of the Supplier
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="50"
     **/
    private String phone_office;

    /** Mobile phone of the Supplier
     *
     * @jdo.field persistence-modifier="persistent"
     * @jdo.column length="50"
     **/
    private String phone_mobile;
  ...
}

Fetch groups need to be declared on the class level. Each @jdo.fetch-group tag must be followed by the attribute name which declares the name of the fetch group to be used later by PersistenceManager.getFetchPlan().addGroup(...). You can choose names as you desire, but you should stick to the usual constraints (no spaces, no special characters etc.).

To put fields into a fetch group, you declare the names in the attribute field-names as a comma-separated list. Spaces in the list are ignored.

A fetch group can include other fetch-groups, which is declared by the attribute fetch-group-names as a comma-separated list (ignoring spaces).

When an object is loaded from datastore, the method postLoad() is called after the default fetch group has been loaded (if the object implements InstanceCallbacks). If later on another fetch group is loaded, no callback will be triggered by default. To get a callback, you can set the attribute post-load to true (default is false). This will cause the postLoad() method to be triggered again, after the according fetch group is fetched from datastore.

The XDoclet support of fetch groups should be part of XDoclet 1.2.3.