Coding Advanced DAOs with Hibernate and
JPA Annotations - The Pattern
In an earlier tutorial, we created a
simple DAO that was designed to facilitate the persistence of a User
object. The UserDAO did the trick, and it was certainly simple enough
to implement, but I have to admit that in its simplicity, it sorta
side-stepped many of the reasons we use DAOs in the first place.
The DAO, or Data Access Object, is an
important design pattern, not simply because it makes the process of
persisting data a bit easier; no, the DAO pattern is important because
it provides a very, very abstract interface that hides both the
underlying database implementation and the mechanism or framework that
is being used to persist data to the database. The UserDAO, created in
an earlier tutorial, wasn't very transparent with regards to how the
underlying persistence mechanism was implemented. The UserDAO leaves
plenty of room for improvement, that's for sure.
Now, the thing I would eventually like to
do in this section is implement a little web based application that
leverages the object model that was created in the previous tutorial.
This web based application will gather personal information from the
end user, and persist that information to the database. Since the to
be developed web based application will be using the object model
developed in the previous tutorial, why not use those same Client,
ClientDetail, Address and Skill classes to demonstrate the proper, and
a very elegant, implementation of the DAO design pattern? Well, I
can't think of any reasons not to, so that's exactly what we will do
this section.
The Highest Level of DAO Abstraction
I'm going to do a little something here
that will totally blow your mind. I'm going to create the most
abstract DAO you've ever seen, namely a GenericDAO interface, which
all DAOs in the enterprise application will implement.
Part of the magic here is going to be the
use of the much loved, Java 5 generics. The interface declaration of
the GenericDAO will not only include the name of the interface, which
is obviously GenericDAO, but it will also specify that all
implementing classes will use two object types, one of which is very
generically defined (thus the term generics) with the uppercase letter
T, and the other type being named ID, which is further described as a
Serializable object.
As we code, the T will
represent the class the DAO manages, so the type T for the SkillDAO
will be the Skill class, and the type T for the ClientDAO will
be the Client class. This syntax might seem a little weird if you're
new to generics, but once you see it all in action, it's all very
elegant, and it all makes a lot of sense.
The GenericDAO interface defines seven
important methods, three of which are finder methods:
- --T findByPrimaryKey(ID id);
- --List<T> findAll(int startIndex, int fetchSize);
- --List<T> findByExample(T exampleInstance, String[]
excludeProperty);
There are also two persistence related
methods:
- --T save(T entity);
- --void delete(T entity);
And finally, there are two transaction
related methods:
- --void beginTransaction();
- --void commitTransaction();
The Amazing GenericDAO Interface
package com.examscam.dao;
import java.io.Serializable;
import java.util.List;
interface GenericDAO < T, ID extends Serializable >{
T findByPrimaryKey( ID id);
List < T > findAll(int startIndex, int fetchSize);
List < T > findByExample( T exampleInstance,
String[] excludeProperty);
T save( T entity);
void delete( T entity);
void beginTransaction();
void commitTransaction();
}
The fabulous thing about the GenericDAO
is that it defines the most important methods and functionality that
our applications need, namely the ability to save, delete and retrieve
entities from our persistence layer.
For specific DAOs, such as the
ClientDetailDAO, or the SkillDAO, you really don't need to define too
many new methods, as the important ones are already defined in the
parent GenericDAO interface. For the most part, DAOs inheriting from
the GenericDAO interface will generally contain little else other than
custom create methods, and extra finder methods that might make sense
for the associated class.
DAOs for the SkillsManagement
Application
So, the SkillsManagement application has
four Java classes, namely the Client, ClientDetail, Address and Skill
classes. As a rule of thumb, we create a DAO for each class defined in
the problem domain, which for us, would equate to a ClientDAO,
ClientDetailDAO, AddressDAO and SkillDAO.
Really, the methods defined in the parent
GenericDAO interface will suffice for our application. Nevertheless,
an additional finder method was added to the ClientDAO to demonstrate
how you can enhance your DAOs with additional methods. You can see the
additional findAllVerified() method in the ClientDAO on the class
diagram.
Code for the Child DAOs
What follows is the code for the four DAO
interfaces, all of which inherit directly from the GenericDAO. While
these are currently unimplemented interfaces, a Hibernate based
implementation will soon be developed. However, these interfaces are
the only things a JSF or Struts or any other application developer
will use when persisting their JavaBeans. As a result, it would be
possible to replace the Hibernate implementation with any other
technology or framework; The client won't be affected, as they will
continue to use the interfaces defined here.
Notice how each interface concretely
defines the generic T type and generic Serializable ID type that was
part of the top level definition in the GenericDAO interface.
package com.examscam.dao;
import com.examscam.model.Client;
public interface ClientDAO
extends GenericDAO <Client, Long> {
public java.util.List<Client> findAllVerified();
}
package com.examscam.dao;
import com.examscam.model.ClientDetail;
public interface ClientDetailDAO
extends GenericDAO <ClientDetail, Long> {
}
package com.examscam.dao;
import com.examscam.model.Address;
public interface AddressDAO
extends GenericDAO <Address, Long> {
}
package com.examscam.dao;
import com.examscam.model.Skill;
public interface SkillDAO
extends GenericDAO<Skill, Long>{
}
Providing the Abstract HibernateDAO
The client applications interacting with
our persistence layer will be completely buffered from the back end
implementation by using nothing other than the DAO interfaces
provided. However, there does need to be a concrete implementation of
the DAO interfaces somewhere. The place we'll put most of the
implementation code is in an abstract HibernateDAO class. The
HibernateDAO will implement as many of the inherited, unimplemented
methods of the GenericDAO as possible.
We're going to have a number of classes
in our domain that will inherit from the HibernateDAO class, namely:
- --the HibernateClientDAO
- --the HibernateClientDetailDAO
- --the HibernateAddressDAO
- --the HibernateSkillDAO
Of course, when a SkillDAO returns
an entity, it must return an entity of type Skill, and when a
ClientDAO returns an entity, it must return an entity of type Client.
So, one of the things that needs to be defined when an inheriting
child DAO is created, is the type of entity with which the child DAO
is intended to work. This will be done through a single argument
constructor that defines the model class, such as Skill.class, with
which the DAO is associated. So, the HibernateDAO, with its
single-argument constructor, would look like this:
public abstract class HibernateDAO <T, ID extends Serializable>
implements GenericDAO <T, ID> {
private Class<T> persistentClass;
public HibernateDAO(Class c) { persistentClass = c; }
}
And a concrete, child DAO, inheriting
from HibernateDAO would look like this:
public class HibernateSkillDAO extends HibernateDAO
<Skill, Long> implements SkillDAO {
public HibernateSkillDAO() { super(Skill.class); }
}
HibernateDAO: Iteration 1
With the property of type Class defined,
and a constructor provided to initialize that property, implementing
the save, delete, findByPrimaryKey, and even the transactional
methods, is just a matter of writing some basic Hibernate
implementation code:
package com.examscam.dao;
import java.io.*;
import java.util.*;
import org.hibernate.*;
import org.hibernate.criterion.*;
import com.examscam.HibernateUtil;
public abstract class HibernateDAO
<T, ID extends Serializable>
implements GenericDAO <T, ID> {
private Class<T> persistentClass;
public HibernateDAO(Class c) {persistentClass = c;}
public T findByPrimaryKey(ID id) {
return (T) HibernateUtil.getSession()
.load(persistentClass, id);
}
public T save(T entity) {
HibernateUtil.getSession().saveOrUpdate(entity);
return entity;
}
public void delete(T entity) {
HibernateUtil.getSession().delete(entity);
}
public void beginTransaction() {
HibernateUtil.beginTransaction();
}
public void commitTransaction() {
HibernateUtil.commitTransaction();
}
}
The findByExample Method
The findByExample method is relatively
straight forward, as it simply leverages the Criteria API, along with
the generic <T> return type, to implement a findByExample method
that would work for any class that extends the HibernateDAO.
public List<T> findByExample(T exampleInstance,
String[] excludeProperty) {
Criteria crit = HibernateUtil.getSession()
.createCriteria(persistentClass);
Example example = Example.create(exampleInstance);
for (int i=0; i< excludeProperty.length; i++) {
example.excludeProperty(excludeProperty[i]);
}
crit.add(example);
return crit.list();
}
The findAll Method
Again, because the HibernateDAO class
doesn't exactly know the type of POJO the subclass will be using, the
generic type notation, < T >, is used to define the return type
for the findAll method. But, other than that little twist, the findAll
method simply leverages the criteria API, the persistentClass instance
variable of the HibernateDAO, and the setFirstResult and setFetchSize
methods of the Criteria object, to implement a very elegant findAll
method:
public List<T> findAll(int startIndex, int fetchSize){
Criteria crit =
HibernateUtil.getSession()
.createCriteria(persistentClass);
crit.setFirstResult(startIndex);
crit.setFetchSize(fetchSize);
return crit.list();
}
HibernateDAO: Iteration 2
package com.examscam.dao;import java.io.*;
import java.util.*;
import org.hibernate.*;
import org.hibernate.criterion.*;
import com.examscam.HibernateUtil;
public abstract class HibernateDAO
<T,ID extends Serializable>
implements GenericDAO <T,ID>{
private Class<T> persistentClass;
public HibernateDAO(Class c) {persistentClass = c;}
public T findByPrimaryKey(ID id) {
return (T) HibernateUtil.getSession()
.load(persistentClass, id);
}
public List<T> findByExample(T exampleInstance,
String[] excludeProperty) {
Criteria crit = HibernateUtil.getSession()
.createCriteria(persistentClass);
Example example = Example.create(exampleInstance);
for (int i=0; i< excludeProperty.length; i++) {
example.excludeProperty(excludeProperty[i]);
}
crit.add(example);
return crit.list();
}
public List<T> findAll(int startIndex, int fetchSize){
Criteria crit = HibernateUtil.getSession()
.createCriteria(persistentClass);
crit.setFirstResult(startIndex);
crit.setFetchSize(fetchSize);
return crit.list();
}
public T save(T entity) {
HibernateUtil.getSession().saveOrUpdate(entity);
return entity;
}
public void delete(T entity) {
HibernateUtil.getSession().delete(entity);
}
public void beginTransaction() {
HibernateUtil.beginTransaction();
}
public void commitTransaction() {
HibernateUtil.commitTransaction();
}
}
The Concrete DAO Classes
With the abstract HibernateDAO providing
concrete implementations for all of the key DAO methods, implementing
the actual, concrete, DAO subclasses is a lead pipe cinch. They simply
need to extend the HibernateDAO class, implement the appropriate DAO
interface, and provide concrete class names for the generic types
defined by the GenericDAO interface. Here they all are, including the
ClientDAO with the additional, implemented, findAllVerified method:
package com.examscam.dao;
import com.examscam.HibernateUtil;
import com.examscam.model.Client;
public class HibernateClientDAO extends HibernateDAO
<Client, Long> implements ClientDAO {
public HibernateClientDAO() { super(Client.class); }
public java.util.List<Client> findAllVerified(){
Client client = new Client(); client.setVerified(true);
return super.findByExample(client, null);
}
}
package com.examscam.dao;
import com.examscam.model.ClientDetail;
public class HibernateClientDetailDAO extends HibernateDAO
<ClientDetail, Long> implements ClientDetailDAO {
public HibernateClientDetailDAO(){super(ClientDetail.class);}
}
package com.examscam.dao;
import com.examscam.model.Address;
public class HibernateAddressDAO extends HibernateDAO
<Address, Long> implements AddressDAO {
public HibernateAddressDAO() {super(Address.class);}
}
package com.examscam.dao;
import com.examscam.model.Skill;
public class HibernateSkillDAO extends
HibernateDAO <Skill, Long> implements SkillDAO {
public HibernateSkillDAO() {super(Skill.class);}
}
Mitigating Client Access to DAOs
Okay, so we've done all of this crazy
coding with interfaces, abstract classes and DAO object, all with the
promise of completely and totally obfuscating the back end
implementation of the persistence layer from the client. I mean,
that's the whole point, right? We have created these simple DAO
interfaces that the client will use, all the while, behind the scenes,
we can swap in or swap out just about any persistence layer
implementation we want.
But at this point, we've got DAO
interfaces, like the ClientDAO, and we have a Hibernate based
implementation through such classes as the concrete
HibernateClientDAO. Right now, if a web developer wants to use the
ClientDAO, they need to write some code that looks like this:
ClientDAO clientDAO = new HibernateClientDAO();
Now, I'm no genius, but just looking at
that line of code, it seems pretty clear to me that Hibernate is being
used to implement the ClientDAO interface. The goal of the DAO pattern
is to hide the back end implementation of the persistence layer.
That's not being done very well if the actual word HIBERNATE is
peppered throughout the client's application.
So, how do we solve the dilemma of not
adequately hiding from the client information about the how the
persistence layer is implemented? Well, that's easy - we just make
sure the client never sees the HibernateClientDAO() class, and
instead, when they need an instance of the ClientDAO, they have to go
through a DAO factory instead. Yes, the GOF (Gang of Four) Factory
design pattern is an integral part of any good DAO design pattern
implementation. The Factory is how we hide the actual DAO
implementation from the client.
The Abstract DAOFactory Class
To gain access to the DAO of interest,
our clients will be given an abstract class called DAOFactory, which
contains abstract methods for accessing each of the DAO classes of
interest, namely the ClientDAO, ClientDetailDAO, SkillDAO and
AddressDAO.
The abstract DAOFactory class will have
one, single, static, invocable method that will return an instantiated
instance of the DAOFactory. It is through this instance that clients
will be able to gain access to the DAOs of interest.
Now one thing that you
should note is that the concrete class that implements the DAOFactory
will be named HibernateDAOFactory. This class is referenced inside the
DAOFactory through the constant variable FACTORY_CLASS. Since this
class hasn't been created yet, attempting to compile the code below
would fail, as the compiler would not be able to find the
HibernateDAOFactory. Don't worry, creating the HibernateDAOFactory is
the next class we'll create!
/*this won't compile until you also create
the HibernateDAOFactory*/
package com.examscam.dao;
public abstract class DAOFactory {
public static final Class FACTORY_CLASS =
com.examscam.dao.HibernateDAOFactory.class;
/*public static final Class FACTORY_CLASS
=JDBCDAOFactory.class;*/
/*public static final Class FACTORY_CLASS
=JDODAOFactory.class;*/
public static DAOFactory getFactory() {
try {
return (DAOFactory)FACTORY_CLASS.newInstance();
} catch (Exception e) {
throw new RuntimeException("Couldn't create Factory");
}
}
public abstract ClientDAO getClientDAO();
public abstract ClientDetailDAO getClientDetailDAO();
public abstract AddressDAO getAddressDAO();
public abstract SkillDAO getSkillDAO();
}
DAO Access through the DAOFactory
The DAOFactory is the class that client
developers will use to gain access to the DAO objects needed to
persist their Java components to the database. So, for a client to
gain access to the ClientDAO, they'd simply code something like this:
DAOFactory factory = DAOFactory.getFactory();
ClientDAO clientDAO = factory.getClientDAO();
As you can see, there is absolutely no
reference here to the fact that the underlying persistence layer is
implemented through Hibernate. And what's more, the underlying
implementation could be changed from Hibernate to JDO or to JDBC quite
easily, so long as the same DAO interfaces, such as the ClientDAO or
SkillDAO interfaces, are implemented. So, we gain infinite flexibility
on the data side with regards to how the persistence
layer is managed, and we get complete persistence layer independence
on the client side of the application as well. The DAO
pattern, in cahoots with the factory pattern, really demonstrates the
actualization of the separation of concerns to which all enterprise
architects and developers aspire.
The DAOFactory's getFactory() Method
One of the most important aspects of this
design is the manner in which the static getFactory
method of the DAOFactory is implemented. As you can see, the
getFactory method returns an instance of a class that implements the
abstract methods that are defined in the DAOFactory class. The class
type is coded as a static final class variable in the DAOFactory, and
instantiated and returned from the getFactory method. Now, this is all
hidden from the client, so they don't know that the implementation of
the DAOFactory is actually the yet to be coded
HibernateDAOFactory class. In fact, we could switch that factory class
with perhaps a JDBCDAOFactory class, or a JDODAOFactory
class, or whatever factory class implementation we wanted, and the
client would be none the wiser! Really, this is an extremely elegant
design, bringing to fruition a true separation of concerns.
public static final Class FACTORY_CLASS =
com.examscam.dao.HibernateDAOFactory.class;
/*public static final Class FACTORY_CLASS=JDBCDAOFactory.class;*/
/*public static final Class FACTORY_CLASS=JDODAOFactory.class;*/
public static DAOFactory getFactory() {
try {
return (DAOFactory)FACTORY_CLASS.newInstance();
} catch (Exception e) {
throw new RuntimeException("Couldn't create Factory");
}
}
The HibernateDAOFactory
In order for the client developers to
gain access to the data objects they need to persist Client,
ClientDetail, Address and Skill objects, all they need is the
DAOFactory, as it defines the various methods used to access the
corresponding DAO objects. However, the methods defined in the
DAOFactory are abstract, and we need a class to provide a
concrete implementation of the getClientDAO(), getClientDetailDAO(),
getSkillDAO() and the getAddressDAO() methods. Of course, Hibernate
has been providing all of our concrete implementations thus far, so it
just makes sense that it will deliver to us a concrete implementation
of the abstract DAOFactory class as well.
As you can see, there's really nothing
too earth shattering about the HibernateDAOFactory. All it really does
is implement the getXYZDAO methods by returning the
corresponding HibernateDAO that implements the interface required. So,
for example, with the getClientDAO() method, the HibernateDAOFactory
simply returns an instance of the HibernateClientDAO, which itself
implements the ClientDAO interface.
public ClientDAO getClientDAO() {
return new HibernateClientDAO();
}
The HibernateDAOFactory Class
package com.examscam.dao;
public class HibernateDAOFactory
extends DAOFactory {
public ClientDAO getClientDAO() {
return new HibernateClientDAO();
}
public ClientDetailDAO getClientDetailDAO() {
return new HibernateClientDetailDAO();
}
public AddressDAO getAddressDAO() {
return new HibernateAddressDAO();
}
public SkillDAO getSkillDAO() {
return new HibernateSkillDAO();
}
}
A Simple, Stand-Alone, Skills Manager
App
So, the promise of the DAO and Factory
patterns was the idea that the persistence framework and the back-end
database implementation could be completely shielded from the client
applications interacting with the DAOs. Well, let's put that theory
into practice.
Take a look at the code for the
SkillManagerApp. It's a simple class with nothing more than a runnable
main method that creates a Client, a ClientDetail, an Address, and a
few Skill objects:
Client client = new Client();
ClientDetail detail = new ClientDetail();
client.setClientDetail(detail);
Address address = new Address();
address.setClient(client);
client.getAddresses().add(address);
Skill basting = new Skill();
basting.setName("turkey basting");
client.getSkills().add(basting);
However, by using the DAO interfaces and
abstract classes that are involved in the implementation of the DAO
and factory patterns, saving the state of these JavaBean instances
becomes very simple, and there is no indication that Hibernate is
doing the work under the covers:
DAOFactory factory = DAOFactory.getFactory();
ClientDAO clientDAO = factory.getClientDAO();
ClientDetailDAO clientDetailDAO = factory.getClientDetailDAO();
SkillDAO skillDAO = factory.getSkillDAO();
AddressDAO addressDAO = factory.getAddressDAO();
clientDAO.save(client);
clientDetailDAO.save(detail);
addressDAO.save(address);
skillDAO.save(basting);
The Stand-Alone SkillManagerApp
package com.examscam.dao;
import com.examscam.model.*;
public class SkillManagerApp {
public static void main(String args[]) {
DAOFactory factory = DAOFactory.getFactory();
factory.getClientDAO().beginTransaction();
ClientDAO clientDAO = factory.getClientDAO();
ClientDetailDAO clientDetailDAO =
factory.getClientDetailDAO();
SkillDAO skillDAO = factory.getSkillDAO();
AddressDAO addressDAO = factory.getAddressDAO();
Client client = new Client();
client.setUsername("me");
client.setPassword("passw0rd");
ClientDetail detail = new ClientDetail();
detail.setEmailAddress("mail@scja.com");
detail.setFirstName("Cameron");
detail.setLastName("McKenzie");
client.setClientDetail(detail);
Address address = new Address();
address.setAddressLine1("390 Queens Quay");
address.setAddressLine2("apt 2301");
address.setCity("Toronto");
address.setCountry("Canada");
address.setClient(client);
client.getAddresses().add(address);
Skill basting = new Skill();
basting.setName("turkey basting");
client.getSkills().add(basting);
Skill kicking = new Skill();
kicking.setName("tire kicking");
/* kicking not added as a skill */
Skill polishing = new Skill();
polishing.setName("shoe polishing");
client.getSkills().add(polishing);
clientDAO.save(client);
clientDetailDAO.save(detail);
addressDAO.save(address);
skillDAO.save(basting);
skillDAO.save(kicking);
skillDAO.save(polishing);
factory.getClientDAO().commitTransaction();
}
}
Inspecting the Database
After running the SkillManagerApp, we can
quickly inspect the database to ensure that all of the tables have
been populated with the appropriate data fields.
As you can see from the collage of screen
captures generated by the MySQL GUI Browser tool, data has been
populated in all of the tables used by this application, namely the
client, client_detail, address, skill, and even the client_skill
table. Primary keys and foreign keys all map back to one another to
maintain the associations set up in the runnable main method of the
SkillManagerApp class.
|