|
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.
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.
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
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.
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.
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.
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"
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.
|
|