DataNucleus - Design of a DAO Layer with JDO

Introduction

The design of an application will involve many choices, and often compromises. What is generally accepted as good practice is to layer the application tiers and provide interfaces between these. For example a typical web application can have 3 tiers - web tier, business-logic tier, and data-access tier. DataNucleus provides data persistence and so, following this model, should only be present in the data access layer. One pattern to achieve this is the data access object (DAO) pattern.

A typical DAO provides an interface that defines its contract with the outside world. This takes the form of a series of data access and data update methods. In this tutorial we follow this and describe how to implement a DAO using DataNucleus and JDO using some of the new features introduced in JDO 2.0

While this guide demonstrates how to write a DAO layer, it does not propose use of the DAO pattern, just explaining how you could achieve it


The DAO contract

To highlight our strategy for DAO's we introduce 3 simple classes.

public class Owner
{
    private Long id;
    private String firstName;
    private String lastName;
    private Set<Owner> pets;
    
    public void addPet(Pet pet)
    {
        pets.add(pet);
    }
    ...
}

public class Pet
{
    private Long id;
    private PetType type;
    private String name;
    private Owner owner;

    public void setType(PetType type)
    {
        this.type = type;
    }
    ...
}

public class PetType
{
    private String name;
    ...
}

So we have 3 dependent classes, and we have a 1-N relationship between Owner and Pet, and a N-1 relationship between Pet and PetType.

We now generate an outline DAO object containing the main methods that we expect to need

public interface ClinicDAO
{
    public Collection<Owner> getOwners();
    public Collection<PetType> getPetTypes();
    public Collection<owner> findOwners(String lastName);
    public Owner loadOwner(long id);
    public void storeOwner(Owner owner);
    public void storePet(Pet pet);
}

Clearly we could have defined more methods, but these will demonstrate the basic operations performed in a typical application. Note that we defined our DAO as an interface. This has various benefits, and the one we highlight here is that we can now provide an implementation using DataNucleus and JDO. We could, in principle, provide a DAO implementation of this interface using JDBC for example, or one for whatever persistence technology. It demonstrates a flexible design strategy allowing components to be swapped at a future date.

We now define an outline DAO implementation using DataNucleus. We will implement just a few of the methods defined in the interface, just to highlight the style used. So we choose one method that retrieves data and one that stores data.

public class MyDAO implements ClinicDAO
{
    PersistenceManagerFactory pmf;
    
    /** Constructor, defining the PersistenceManagerFactory to use. */
    public MyDAO(PersistenceManagerFactory pmf)
    {
        this.pmf = pmf;
    }
    
    /** Accessor for a PersistenceManager */
    protected PersistenceManager getPersistenceManager()
    {
        return pmf.getPersistenceManager();
    }
    
    public Collection<Owner> getOwners()
    {
        Collection owners = null;
        PersistenceManager pm = getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        try
        {
            tx.begin();

            Query q = pm.newQuery(mydomain.model.Owner.class);
            Collection query_owners = q.execute();

            // *** TODO Copy "query_owners" into "owners" ***

            tx.commit();
        }
        finally
        {
            if (tx.isActive())
            {
                tx.rollback();
            }
            pm.close();
        }
        return owners;
    }
    
    public void storeOwner(Owner owner)
    {
        PersistenceManager pm = getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        try
        {
            tx.begin();

            // Owner is new, so persist it
            if (owner.id() == null)
            {
                pm.makePersistent(owner);
            }
            // Owner exists, so update it
            else
            {
                // *** TODO Store the updated owner ***
            }
            
            tx.commit();
        }
        finally
        {
            if (tx.isActive())
            {
                tx.rollback();
            }
            pm.close();
        }
    }
    
    ...
}

So here we've seen the typical DAO and how, for each method, we retrieve a PersistenceManager, obtain a transaction, and perform our operation(s). Notice above there are a couple of places where we have left "TODO" comments. These will be populated in the next section, using the JDO 2.0 feature attach/detach.

Use of attach/detach

We saw in the previous section our process for the DAO methods. The problem we have with JDO 1.0 is that as soon as we leave the transaction our object would move back to "Hollow" state (hence losing its field values, and hence the object would have been unusable in the rest of our application. With JDO 2.0 we have a feature called attach/detach that allows us to detach objects for use elsewhere, and then attach them when we want to persist any changed data within the object. So we now go back to our DataNucleus DAO and add the necessary code to use this

    public Collection<Owner> getOwners()
    {
        Collection<Owner> owners;
        PersistenceManager pm = getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        try
        {
            tx.begin();

            Query q = pm.newQuery(mydomain.model.Owner.class);
            Collection query_owners = q.execute();

            // Detach our owner objects for use elsewhere
            owners = pm.detachCopyAll(query_owners);

            tx.commit();
        }
        finally
        {
            if (tx.isActive())
            {
                tx.rollback();
            }
            pm.close();
        }
        return owners;
    }
    
    public void storeOwner(Owner owner)
    {
        PersistenceManager pm = getPersistenceManager();
        Transaction tx = pm.currentTransaction();
        try
        {
            tx.begin();

            // Persist our changes back to the datastore
            pm.makePersistent(owner);

            tx.commit();
        }
        finally
        {
            if (tx.isActive())
            {
                tx.rollback();
            }
            pm.close();
        }
    }
}

So we have added 2 very simple method calls. These facilitate making our objects usable outside the DAO layer and so give us much flexibility. Please note that instead of detachCopy you could set the PMF option "javax.jdo.option.DetachAllOnCommit" and this would silently migrate all enlisted instances to detached state at commit of the transaction, and is probably a more convenient way of detaching.

Definition of fetch-groups

In the previous section we have described how to design our basic DAO layer. We have an interface to this layer and provide a DataNucleus implementation. The DataNucleus/JDO calls are restricted to the DAO layer. What we haven't yet considered is what actually is made usable in the rest of the application when we do our detach. By default with this feature PersistenceCapable fields will not be detached with the owning object. This means that our "pets" field of our detached "owner" object will not be available for use. In many situations we would want to give access to other parts of our object. To do this we make use of another JDO 2.0 feature, called fetch-groups. This is defined both in the Meta-Data for our classes, and in the DAO layer where we perform the detaching. Let's start with the MetaData for our 3 classes.

    <class name="Owner" detachable="true">
        <field name="id" primary-key="true"/>
        <field name="pets" mapped-by="owner">
            <collection element-type="mydomain.model.Pet"/>
        </field>

        <fetch-group name="detach_owner_pets">
            <field name="pets"/>
        </fetch-group>
    </class>
    <class name="PetType" detachable="true">
        <field name="id" primary-key="true"/>
        <field name="name">
            <column length="80" jdbc-type="VARCHAR"/>
        </field>
    </class>
    <class name="Pet" detachable="true">
        <field name="id" primary-key="true"/>
        <field name="name">
            <column length="30" jdbc-type="VARCHAR"/>
        </field>
        <field name="type" persistence-modifier="persistent"/>

        <fetch-group name="detach_pet_type">
            <field name="type"/>
        </fetch-group>
    </class>

Here we've marked the classes as detachable, and added a fetch-group to Owner for the "pets" field, and to Pet for the "type" field. Doing these for each field adds extra flexibility to our ability to specify them. Lets now update our DAO layer method using detach to use these fetch-groups so that when we use the detached objects in our application, they have the necessary components available.

    public Collection getOwners()
    {
        Collection owners;
        PersistenceManager pm=getPersistenceManager();
        Transaction tx=pm.currentTransaction();
        try
        {
            tx.begin();

            Query q = pm.newQuery(mydomain.model.Owner.class);
            Collection query_owners = q.execute();

            // Define the objects to be detached with our owner objects.
            pm.getFetchPlan().addGroup("detach_owner_pets");
            pm.getFetchPlan().addGroup("detach_pet_type");
            pm.getFetchPlan().setMaxFetchDepth(3);
 
            // Detach our owner objects for use elsewhere
            owners = pm.detachCopyAll(query_owners);
            
            tx.commit();
        }
        finally
        {
            if (tx.isActive())
            {
                tx.rollback();
            }
            pm.close();
        }
        return owners;
    }

So you see that when we detach our Owner objects, we also detach the Pet objects for each owner and for each Pet object we will also detach the PetType object. This means that we can access all of these objects with no problem outside of the DAO layer.

Summary

In this tutorial we've demonstrated a way of separating the persistence code from the rest of the application by providing a DAO layer interface. We've demonstrated how to write the associated methods within this layer and, with the use of attach/detach and fetch-groups we can make our persisted objects available outside of the DAO layer in a seamless way. Developers of the remainder of the system don't need to know any details of the persistence, they simply code to the interface contract provided by the DAO.