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 will generally 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. This layer is generally designed using 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
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 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 getOwners();
public Collection getPetTypes();
public Collection 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 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();
// *** 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
.
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 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();
// 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.
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" identity-type="application"
objectid-class="mydomain.model.key.OwnerKey">
<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" identity-type="datastore">
<field name="name">
<column length="80" jdbc-type="VARCHAR"/>
</field>
</class>
<class name="Pet" detachable="true" identity-type="application"
objectid-class="mydomain.model.key.PetKey">
<field name="id" primary-key="true" value-strategy="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();
// Define the objects to be fetched/detached with our owner objects.
pm.getFetchPlan().addGroup("detach_owner_pets");
pm.getFetchPlan().addGroup("detach_pet_type");
pm.getFetchPlan().setMaxFetchDepth(3);
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;
}
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.
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.