DataNucleus and the SpringFramework

The Spring Framework provides a mechanism for designing systems, aiding you in the modularisation of components and hence in making components more readily testable. The crux of the framework is that you design your business service and data access objects as Java beans, and then provide a dependency mapping between them. This leads to very well structured systems with a pluggable feel.

The most important thing to mention is that you must use Spring 2.0 or later.

Spring Framework is suited to a wide variety of architectures, and can be utilised in discrete parts of a system, as well as across the whole system. Let's give an example where we want to use Spring for the business and data access tiers of a system.

In our system we have a typical business service SampleService , which utilises a data-access SampleDAO . We define these as Java Beans (default constructor, and properties with getters/setters). Once we have defined our beans we then define the "glue" to link them together. This is performed via an XML configuration file. There are many ways you can utilise Spring in this respect. Here's our definition using what is typically the simplest pattern. This file is used to create an ApplicationContext which loads all of these beans at startup automatically.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- PMF Bean -->
    <bean id="pmf"
        class="org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean">
        <property name="jdoProperties">
            <props>
                <prop key="javax.jdo.PersistenceManagerFactoryClass">
                    org.jpox.jdo.JDOPersistenceManagerFactory</prop>
                <prop key="javax.jdo.option.ConnectionURL">jdbc:mysql://localhost/dbname</prop>
                <prop key="javax.jdo.option.ConnectionUserName">username</prop>
                <prop key="javax.jdo.option.ConnectionPassword">password</prop>
                <prop key="javax.jdo.option.ConnectionDriverName">com.mysql.jdbc.Driver</prop>
            </props>
        </property>
    </bean>

    <!-- Transaction Manager for PMF -->
    <bean id="jdoTransactionManager" class="org.springframework.orm.jdo.JdoTransactionManager">
        <property name="persistenceManagerFactory">
            <ref local="pmf"/>
        </property>
    </bean>

    <!-- Typical DAO -->
    <bean id="sampleDAO" class="org.jpox.spring.SampleDAO">
        <property name="persistenceManagerFactory">
            <ref local="pmf"/>
        </property>
    </bean>

    <!-- Typical Business Service -->
    <bean id="sampleService" class="org.jpox.spring.SampleService">
        <property name="sampleDAO">
            <ref local="sampleDAO"/>
        </property>
    </bean>

    <!-- Transaction Interceptor for Business Services -->
    <bean id="transactionInterceptor" 
        class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager">
            <ref local="jdoTransactionManager">
        </property>
        <property name="target">
            <ref local="sampleService">
        </property>
        <property name="transactionAttributes">
            <props>
                <prop key="store*">PROPAGATION_REQUIRED</prop>
                <prop key="delete*">PROPAGATION_REQUIRED</prop>
                <prop key="*">PROPAGATION_REQUIRED,readOnly</prop>
            </props>
        </property>
    </bean>
</beans>

Here we've defined our SampleService to have a dependency on SampleDAO . In turn, SampleDAO has a dependency on the PersistenceManagerFactory. We've opted to use Spring's transaction handling capability, so we define a JdoTransactionManager for the PersistenceManagerFactory and in addition, we define a transaction interceptor around the SampleService which couples our transactions with those of Spring.

Design of a JDO DAO

The design of your data persistence layer should be the only place you actually interact with your JDO implementation (e.g DataNucleus). Outside of the DAO your Java objects should be just that, Java objects. This allows you to delimit the impact of your choice of data persistence, and leaves a clean interface to the rest of your system. It provides you the flexibility to swap in a new data persistence strategy in the future.

The choice of JDO for data persistence implies certain operations within this layer so as to provide your clean interface. The JDO 2.0 specification provides 2 important changes to make this simpler, namely the attach/detach capability, and the use of fetch-groups . To give an example of a sample DAO using JDO, ...

public class SampleDAO extends JdoDaoSupport
{
    /** Accessor for a collection of objects */
    public Collection getWorkers()
    throws DataAccessException
    {
        Collection workers = getJdoTemplate().find(Worker.class, null, 
            "lastName ascending");
        workers = getPersistenceManager().detachCopyAll();
        return workers;
    }

    /** Accessor for a specified object */
    public Worker loadWorker(long id)
    throws DataAccessException
    {
        Worker worker =
            (Worker)getJdoTemplate().getObjectById(Worker.class, new Long(id));
        if (worker == null)
        {
            throw new RuntimeException("Worker " + id + " not found");
        }
        return (Worker) getPersistenceManager().detachCopy(worker);
    }

    /** Save/Update an object */
    public void storeWorker(Worker worker)
    throws DataAccessException
    {
        getJdoTemplate().makePersistent(worker);
    }

    /** Delete an object. */
    public void deleteWorker(Worker worker)
    throws DataAccessException
    {
        if (worker == null || worker.getId() == null)
        {
            throw new RuntimeException("Worker is not persistent");
        }
        else
        {
            getPersistenceManager().deletePersistent(worker);
        }
    }
}

The example above demonstrates the 4 most common types of data accessor methods ... namely retrieval of a collection of objects based on a query, load of an object given an id, the saving/updating of an object, and the deletion of the object. Use of the Spring JdoTemplate provides wrapping of the majority of JDO methods required by an application. The DAO extends the Spring JdoDaoSupport class, giving it the benefits of Spring's facilities. The use of attach/detach is highlighted above. Whenever an object is retrieved from the datastore and needs to be used in the application, it is detached from the persistence graph using the detachCopy/detachCopyAll methods. Whenever an object needs persisting, if it is new then it is persisted directly, and otherwise it is reattached to the persistence graph and the datastore updated with any changes.

The other aspect to note is where you have relationships between your Java objects. In this case you need to define a fetch-group defining which objects in a relationship are loaded when you detach the objects from the persistence graph. That is, which related objects you will be needing to use within your application. This is specified in the JDO Meta-Data for your Java classes.



JPOXClinic

We now demonstrate the above techniques in a real application. Here we take the Spring Framework example of Petclinic and adapt it to use with a JDO persistence layer (here we use DataNucleus, naturally!). Note that while this sample is actually for JPOX, you could easily take it and run with DataNucleus



Changes to Petclinic

In working to make Petclinic work with DataNucleus, it was necessary to make some minor changes to the original Spring codebase. These are detailed below.

  • The data model classes are now moved to a package model to better separate the components of the system. In addition the DAO interface and its JDO implementation are in a package dao .
  • The Petclinic build used Ant, and had no provision for byte-code enhancement of the model classes. The build provided with DataNucleusClinic uses Maven and has the enhancement included. This happens automatically when you compile.
  • DataNucleus provides the facility to create the database schema automatically at runtime, or via the SchemaTool. As a result we don't provide schema installation scripts, just a populate script.


Model Configuration

In DataNucleusClinic we have the following persisted classes

  • Person - superclass of an Owner / Vet
  • Owner - The owner of a collection of Pet s.
  • Pet - An animal with an owner (1-N relationship). Has a PetType . Has a collection of Visit s to the Vet
  • PetType - Type of a Pet
  • Vet - A person who repairs Pet s when they need it. Has a collection of Specialty s.
  • Visit - A visit of a Pet to a Vet
  • Specialty - A category of veterinary science that a Vet specialises in
  • Entity - base class that provides an identity.
  • NamedEntity - extension of Entity adding a name.

In our JDO Meta-Data we define which fields are to be persisted, which fields relate to which other objects, and how we will retrieve these related objects (using fetch-group s). You will see that the Entity and NamedEntity classes don't have their own tables.

<jdo>
    <package name="org.jpox.samples.jpoxclinic.model">

        <class name="Entity" detachable="true" identity-type="application">
            <inheritance strategy="subclass-table"/>
            <field name="id" primary-key="true" value-strategy="identity"/>
        </class>

        <class name="NamedEntity" detachable="true" identity-type="application">
            <inheritance strategy="subclass-table"/>
            <field name="name" column="name">
                <column length="80" jdbc-type="VARCHAR"/>
            </field>
        </class>

        <!-- Person Class. Map to table "person" (subclasses) -->
        <class name="Person" detachable="true" table="person">
            <inheritance strategy="new-table"/>
            <field name="firstName">
                <column length="30" jdbc-type="VARCHAR"/>
            </field>
            <field name="lastName">
                <column length="30" jdbc-type="VARCHAR"/>
            </field>
            <field name="address">
                <column length="255" jdbc-type="VARCHAR"/>
            </field>
            <field name="city">
                <column length="80" jdbc-type="VARCHAR"/>
            </field>
            <field name="telephone">
                <column length="20" jdbc-type="VARCHAR"/>
            </field>
        </class>

        <!-- Owner Class. Map to table "owners" and provide fetch-group -->
        <class name="Owner" detachable="true" table="owners" identity-type="application">
            <inheritance strategy="new-table"/>
            <field name="pets" mapped-by="owner">
                <collection element-type="org.jpox.samples.jpoxclinic.model.Pet"/>
            </field>
            <fetch-group name="detach_owner_pets">
                <field name="pets"/>
            </fetch-group>
        </class>

        <!-- Vet Class. Map to table "vets" and provide fetch-group -->
        <class name="Vet" detachable="true" table="vets" identity-type="application">
            <inheritance strategy="new-table"/>
            <field name="specialties" table="vet_specialties">
                <collection element-type="org.jpox.samples.jpoxclinic.model.Specialty"/>
                <join>
                    <column name="vet_id"/>
                </join>
                <element>
                    <column name="specialty_id"/>
                </element>
            </field>
            <fetch-group name="detach_vet_specialties">
                <field name="specialties"/>
            </fetch-group>
        </class>

        <!-- Specialty Class. Map to table "specialties" and provide fetch-group -->
        <class name="Specialty" detachable="true" table="specialties" identity-type="application">
            <inheritance strategy="new-table"/>
            <field name="vets" table="vet_specialties">
                <collection element-type="org.jpox.samples.jpoxclinic.model.Vet"/>
                <join>
                    <column name="specialty_id"/>
                </join>
                <element>
                    <column name="vet_id"/>
                </element>
            </field>
            <fetch-group name="detach_specialty_vets">
                <field name="vets"/>
            </fetch-group>
        </class>

        <!-- PetType Class. Map to table "types" -->
        <class name="PetType" detachable="true" table="types" identity-type="application">
            <inheritance strategy="new-table"/>
        </class>

        <!-- Pet Class. Map to table "pets" -->
        <class name="Pet" detachable="true" table="pets" identity-type="application">
            <inheritance strategy="new-table"/>
            <field name="birthDate" column="birth_date"/>
            <field name="owner" column="owner_id" persistence-modifier="persistent"/>
            <field name="type" column="type_id" persistence-modifier="persistent"/>
            <field name="visits" mapped-by="pet">
                <collection element-type="org.jpox.samples.jpoxclinic.model.Visit"/>
            </field>

            <fetch-group name="detach_pet">
                <field name="type"/>
            </fetch-group>
            <fetch-group name="detach_pet_owner">
                <field name="owner"/>
            </fetch-group>
            <fetch-group name="detach_pet_visits">
                <field name="visits"/>
            </fetch-group>
        </class>

        <class name="Visit" detachable="true" table="visits" identity-type="application">
            <inheritance strategy="new-table"/>
            <field name="date" column="visit_date"/>
            <field name="description" column="description">
                <column length="255" jdbc-type="VARCHAR"/>
            </field>
            <field name="pet" column="pet_id" persistence-modifier="persistent"/>

            <fetch-group name="detach_visit_pet">
                <field name="pet"/>
            </fetch-group>
        </class>
    </package>
</jdo>

The other thing to note from this MetaData is the use of 2 other new features of JDO 2. The first is called SingleFieldIdentity and means that where we have only a single primary key field, we no longer need to provide a primary key class, and so we miss off the objectid-class attribute. The second feature is part of the JDO 2 O/R mapping definition. You note that we have 2 classes Entity and NamedEntity and we don't want these to have their own tables in the database. We simply define them as having an inheritance strategy of subclass-table . This means that their fields will be persisted in the table of the next subclass that does have its own table.



Data Access Object Configuration

As mentioned in the description of how to design a dataaccess layer with JDO we need to make use of the JDO 2.0 features attach/detach and fetch-groups . With JPOXClinic we use this strategy. Lets take the example of a finder for the Owner class.

    public Collection findOwners(String lastName) throws DataAccessException
    {
        getPersistenceManager().getFetchPlan().addGroup("detach_owner_pets");
        getPersistenceManager().getFetchPlan().addGroup("detach_pet");
        getPersistenceManager().getFetchPlan().addGroup("detach_pet_visits");
        getPersistenceManager().getFetchPlan().setMaxFetchDepth(4);
        Map values = new HashMap();
        values.put("value",lastName);
        Collection owners = 
            getJdoTemplate().find(Owner.class, "lastName == value","String value",values);
        owners = getPersistenceManager().detachCopyAll(owners);
        return owners;
    }

So we define that when we fetch our Owner objects, we retrieve the "pets" related objects, and for each of them we retrieve the "petType", and the "visits" for each Pet . We then detach the Owner objects (and with them the Pet objects) so that we can use them in our application.

This demonstrates the power of the fetch-group and attach/detach functionality. If you look at the other methods in the JPOXClinic DAO you will find a similar methodology applied throughout.



Spring Configuration

Lets look at how we configure Spring in terms of dependency injection. We define our JPOXClinic class as the DAO bean, having a dependency on the PersistenceManagerFactory, and the PersistenceManagerFactory taking the standard DataNucleus properties for the datastore and to auto-generate the datastore schema (if not already existing). Finally we define Spring's transaction interceptor to provide all transaction handling so we don't have to write the code for this ourselves.

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
    "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- DataNucleus PersistenceManagerFactory -->
    <bean id="pmf" class="org.springframework.orm.jdo.LocalPersistenceManagerFactoryBean">
        <property name="jdoProperties">
            <props>
                <prop key="javax.jdo.PersistenceManagerFactoryClass">
                    org.jpox.jdo.JDOPersistenceManagerFactory</prop>
                <prop key="javax.jdo.option.ConnectionURL">jdbc:mysql://localhost/petclinic</prop>
                <prop key="javax.jdo.option.ConnectionUserName">pc</prop>
                <prop key="javax.jdo.option.ConnectionPassword"></prop>
                <prop key="javax.jdo.option.ConnectionDriverName">com.mysql.jdbc.Driver</prop>
                <prop key="org.jpox.autoCreateSchema">true</prop>
                <prop key="org.jpox.identifier.case">PreserveCase</prop>
            </props>
        </property>
    </bean>

    <!-- Transaction manager for a single DataNucleus PMF -->
    <bean id="transactionManager" class="org.springframework.orm.jdo.JdoTransactionManager">
        <property name="persistenceManagerFactory"><ref local="pmf"/></property>
    </bean>

    <!-- JPOXClinic primary DAO -->
    <bean id="clinicTarget" class="org.jpox.samples.jpoxclinic.dao.JPOXClinic">
        <property name="persistenceManagerFactory"><ref local="pmf"/></property>
    </bean>

    <!-- Transactional proxy for the JPOXClinic primary DAO -->
    <bean id="clinic" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
        <property name="transactionManager"><ref local="transactionManager"/></property>
        <property name="target"><ref local="clinicTarget"/></property>
        <property name="transactionAttributes">
            <props>
                <prop key="get*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="find*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="load*">PROPAGATION_REQUIRED,readOnly</prop>
                <prop key="store*">PROPAGATION_REQUIRED</prop>
                <prop key="delete*">PROPAGATION_REQUIRED</prop>
            </props>
        </property>
    </bean>
</beans>

The final part of this defines which methods Spring manages the transactions for, and how it manages them. So it will manage transactions for all methods starting "get", "find", "load", "store", and "delete"



Summary

From this brief tutorial you have seen how we have designed a simple data model and how we configured it for data persistence by DataNucleus. We then defined a data access layer for our application, and finally configured our data access layer and model providing dependency injection and transactions, giving us a very flexible application structure. We have only really touched on some of Spring's capabilities, and you should refer to the Spring Framework project for its full capabilities. You can download the JPOXClinic sample application from SourceForge.

Much of the above example code uses "JdoTemplate" which wraps JDO methods. It is arguable as to the benefit of this and indeed the fact that SpringFramework is sometimes behind JDO in terms of support we recommend using Spring for providing the transaction handling, and then just use JdoTemplate to get hold of the PersistenceManager, thereafter using JDO methods directly.