There's no debating the fact that Hibernate greatly simplifies the job of managing the persistent state of the classes in your domain model. With access to a Hibernate Session, it's pretty easy to perform a quick query, save a POJO, or update a record in the database. However, as elegant and powerful as Hibernate is, it's sometimes polite to hand over some very simple Data Access Objects (DAOs) to your application integrators so they can more easily, and consistently, interact with the underlying datasource. Furthermore, Data Access Objects can hide the underlying implementation of the persistence framework from the end user, making it easier to implement future changes to the database layer. In fact, the concepts of creating Data Access Objects for you application integrators is so commonly accepted that it considered one of the most fundamental of all of the design patterns.
In this tutorial, we'll take a first pass at creating Data Access Objects that will help developers interact with the underlying datastore, subsequently shielding developers and application integrators from the implementation logic in the Hibernate layer.
The UserDAO Interface
At the heart of the DAO design pattern is the idea that client applications will be provided simple Java interfaces for accessing data, with the actual implementation of those interfaces being performed by classes that can be easily switched in and out of the application if the need ever arises. As such, it is necessary to define all of the methods that will be used for mitigating access to the back end database in a DAO interface.
Our UserDAO interface will define six methods: create, update, delete, findAll, findByPrimaryKey and finally, the findByExample method which will leverage our newfound knowledge of the Criteria API.
package com.examscam.dao;
import java.util.List;
import com.examscam.model.User;
public interface UserDAO {
public User create(User user);
public boolean update(User user) ;
public boolean delete(User user) ;
public User findByPrimaryKey(Long primaryKey);
public List findByExample(User user, boolean fuzzy);
public List findAll();
}Generic Data Access Object (DAO)
Methods
Factoring out common behavior into reusable methods is always a fundamental goal. Our domain model currently only has one class defined in it, the User class, but we could easily imagine more and more classes being added over time. As such, when we create our DAOs, we should bear in mind that additional classes will eventually make it into our framework. With this thought in mind, any Hibernate based implementation of the DAO pattern should factor out CRUD related commonality into a reusable, abstract, inheritable, parent class.
For our purposes, we will code and implement four important methods in an abstract class called the ExamScamDAO. Those methods will be: getSession, save, delete and the findByPrimaryKey method.
Methods of the ExamScamDAO Class
The getSession() method of the ExamScamDAO is simply a helper method that encapsulates the call to the getSession() method of the HibernateUtil class. There's nothing very sexy or salacious to see here.
protected Session getSession() {
return HibernateUtil.getSession();
}
The delete and save methods are also relatively simple, simply taking any JPA annotated POJO and either saving or deleting the state of that POJO to the database. Since Hibernate does introspection on the bean being passed to the Hibernate Session's delete, save or update methods, we don't need to explicitly specify the object type in the argument list of the ExamScamDAO's save or delete methods. All we require is that the subclassing DAOs pass in a JPA annotated JavaBean.
protected void save(Object pojo) {
Session hibernateSession = this.getSession();
hibernateSession.saveOrUpdate(pojo);
}
protected void delete(Object pojo) {
Session hibernateSession = this.getSession();
hibernateSession.delete(pojo);
}
Finally, given a valid primary key that is passed in as an argument, the findByPrimaryKey method simply grabs an instance of a JPA annotated POJO from the underlying database. The class
of the type must be passed into the method as well, as Hibernate cannot ascertain the object type of interest based solely on a numeric primary key. The findByPrimaryKey method must be passed both a primary key, and a definition of the
class type of the object being queried.
protected Object
findByPrimaryKey(Class c, Long primaryKey){
Object pojo;
Session hibernateSession = this.getSession();
pojo = hibernateSession.get(c, primaryKey);
return pojo;
}
The Abstract ExamScamDAO Class
package com.examscam.dao;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import com.examscam.ExamScamException;
import com.examscam.HibernateUtil;
public abstract class ExamScamDAO {
protected Session getSession() {
return HibernateUtil.getSession();
}
protected void save(Object pojo) {
Session hibernateSession = this.getSession();
hibernateSession.saveOrUpdate(pojo);
}
protected void delete(Object pojo) {
Session hibernateSession = this.getSession();
hibernateSession.delete(pojo);
}
protected Object
findByPrimaryKey(Class c, Long primaryKey){
Object pojo;
Session hibernateSession = this.getSession();
pojo = hibernateSession.get(c, primaryKey);
return pojo;
}
}
The HibernateUserDAO
The key component in our DAO implementation will be the HibernateUserDAO class, which will provide the concrete implementation of the UserDAO interface. Taking advantage of the methods coded in the parent class, the HibernateUserDAO will extend the ExamScamDAO class, and of course, implement the UserDAO interface.
package com.examscam.dao;
import java.util.List;
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.MatchMode;
import com.examscam.model.User;
public class HibernateUserDAO
extends ExamScamDAO implements UserDAO {
/* class implementation will go here */
}
One of the key benefits to creating a DAO is that it makes interactions with your database, or for that matter, your Hibernate framework, a bit easier for application developers. As such, it's not a bad idea to provide your DAO users with methods that make sense to them. For example, we have seen how Hibernate provides a saveOrUpdate method that will update a record if it already exists, or create a new database record if the object being passed into it does not have a primary key. That's fine for us, because we're intelligent Hibernate developers. But lower life forms such as Servlet and JSP developers might have a hard time with that concept. To help make life easier for our web developers, we can create very simple methods, such as create and update, which our DAO users are likely more familiar and more comfortable. Behind the scenes, we will just call saveOrUpdate in both methods.
More Benefits of Using DAOs
Another benefit of using a DAO is the fact that it is a great place to provide extra little initializations or business logic that your domain model components require. For example, in the create method, we'll be passed in a User object with the basic properties that an end user will type in, such as loginName, emailAddress and password. But, it's our job to set the verified flag to false, and set the registrationDate and lastAccessTime properties to the current time and date. We can hide these details in the create method of the UserDAO class, simplifying the creation process, while at the same time, giving us more control over how new User records are inserted into the database. Data Access Objects really are a great way to mitigate database access for your end users.
Class Diagram for the HibernateUserDAO
To implement the UserDAO interface, we will create a concrete class named HibernateUserDAO that will extend the abstract ExamScamDAO.
Coding the HibernateUserDAO: create
As you can see, the create method of the UserDAO has some funky programming logic in it, helping out with initialization of the User instance.
public User create(User user) {
if (user.getId() != null && user.getId() != 0) {
user = null;
}
else {
user.setLastAccessTime(new java.util.Date());
user.setRegistrationDate
(new java.util.GregorianCalendar());
user.setVerified(false);
super.save(user);
}
return user;
}
At a very high level, we can see that, the create method takes an instance of a user and tries to persist that user to the database. The persisted user, with all of the various fields appropriately initialized, is then returned from the method upon successful completion.
Looking more closely at the code, we will notice a little bit of validation logic at the beginning. If someone passes to the create method a user with an id that is not null and/or not zero, then the create method shouldn't work; after all, if a User instance has a positive, non-zero id, that tells us that the user must already have a representation in the database. If we are indeed passed a user that has an id, we simply nullify the user, bypass the creation logic, and return the nulled out user instance to the calling program.
if (user.getId() != null && user.getId() != 0)
Of course, if the user instance does pass the id inspection, we initialize the lastAccessTime, registrationDate and verified properties, and then pass the appropriately initialized user instance to the parent class' save method.
The HibernateUserDAO update Method
In contrast to the create method, the update method of our DAO checks to ensure that the user instance being passed into the method does indeed have an id associated with it. If a user instance does not have an id, be it null or zero, then Hibernate doesn't know what row to update, and will instead try to create a new record, which sorta goes against the whole idea of an update operation. So, to be safe, the update method checks for an id, and if it can't find one, it sets the success flag to false, bypassing the update operation.
if (user.getId() == null || user.getId() == 0)
Furthermore, the update method catches any exceptions that are thrown, and if the operation fails due to an exception being thrown, the success flag is set to false, and the calling program is informed that the update operation has failed.
public boolean update(User user) {
boolean successFlag = true;
try {
if (user.getId() == null || user.getId() == 0) {
successFlag = false;
}else {
super.save(user);
}
}
catch (Throwable th) {
successFlag = false;
}
return successFlag;
}
The HibernateUserDAO delete Method
Even the delete method of the HibernateUserDAO has some programmatic hanky-panky going on. The delete method is passed a User instance to be deleted; an operation that should be relatively straight forward so long as the instance passed in has a valid id field. However, the password field of the User is annotated as being non-null, so if the password field isn't initialized, even though we're deleting the darned record, Hibernate will choke worse than the NHL's Ottawa Senators. To avoid any problems, we just initialize the password field to a blank String, and then pass the instance of the user to the parent's delete method.
public boolean delete(User user) {
boolean successFlag = true;
try {
user.setPassword(" ");
super.delete(user);
} catch (Throwable th) {successFlag = false;}
return successFlag;
}
HibernateUserDAO findByPrimaryKey
Method
As far as finder methods go, it doesn't get much simpler than the findByPrimaryKey method. The method is simply passed a primary key of type Long, which is then passed to the parent implementation of findByPrimaryKey. The only curve ball is the fact that we must also pass in User.class, just to let the parent know the class type for which we are searching.
public User findByPrimaryKey(Long primaryKey) {
User user = (User)
super.findByPrimaryKey(User.class, primaryKey);
return user;
}
You should also notice that a quick cast, (User), is required on the return of the findByPrimaryKey method. This is because the parent simply returns a lazy little object. We need to remind that lazy little Object that it is intended to be used as a hard working User.
The HibernateUserDAO findAll Method
The findAll method is relatively straight forward, using the "from User" HQL statement that we've seen several times before to generate a java.util.List of User instances:
public List findAll() {
String queryString = "from User";
java.util.List allUsers;
Query queryResult =
this.getSession().createQuery(queryString);
allUsers = queryResult.list();
return allUsers;
}
The HibernateUserDAO: findByExample
The findByExample method represents an interesting use of the Criteria API. Essentially, an instance of the User class is passed into the method, and a query is performed based on the initialized fields of that object. However, there is also a boolean, fuzzy parameter passed into the method as well, and if the value is true, very weak matches will be returned, as the MatchMode is set to ANYWHERE and the casing
of fields is ignored.
public List findByExample(User user, boolean fuzzy) {
List users = null;
Session session = this.getSession();
Criteria criteria =
session.createCriteria(User.class);
Example example = Example.create(user);
if (fuzzy) {
example.enableLike(MatchMode.ANYWHERE);
example.ignoreCase();
example.excludeZeroes();
}
criteria.add(example);
users = criteria.list();
return users;
}
package com.examscam.dao;
import java.util.List; import org.hibernate.*;
import org.hibernate.criterion.*;
import com.examscam.model.User;
public class HibernateUserDAO
extends ExamScamDAO implements UserDAO {
public User create(User user) {
if (user.getId() != null && user.getId() != 0){
user = null;
}
else {
user.setLastAccessTime(new java.util.Date());
user.setRegistrationDate(
new java.util.GregorianCalendar());
user.setVerified(false);
super.save(user);
}
return user;
}
public boolean update(User user) {
boolean successFlag = true;
try {
if (user.getId() == null || user.getId() == 0) {
successFlag = false;
} else {
super.save(user);
}
}
catch (Throwable th) {
successFlag = false;
}
return successFlag;
}
public boolean delete(User user) {
boolean successFlag = true;
try {
user.setPassword( " ");
super.delete(user);
}catch (Throwable th) {
successFlag = false;
}
return successFlag;
}
public User findByPrimaryKey(Long primaryKey) {
User user=(User)super.findByPrimaryKey(
User.class, primaryKey);
return user;
}
public List findByExample(
User user, boolean fuzzy) {
List users = null;
Session session = this.getSession();
Criteria criteria =
session.createCriteria(User.class);
Example example = Example.create(user);
if (fuzzy) {
example.enableLike(MatchMode.ANYWHERE);
example.ignoreCase();
example.excludeZeroes();
}
criteria.add(example);
users = criteria.list();
return users;
}
public List findAll() {
String queryString = "from User ";
Query queryResult =
this.getSession().createQuery(queryString);
return queryResult.list();
}
}
The HibernateUserDAO in Action
Having fully implemented the UserDAO, let's create a simple little stand-alone Java application that uses the UserDAO to persist new User objects to the database.
I'm just going to create a basic class called AddUsers that has a main method, and in that main method, I'll initialize the loginName, emailAddress, and password fields of a User instance based on console input. The rather mundane, non-hibernate part of the class will look like this:
package com.examscam.app;
import org.hibernate.HibernateException;
import com.examscam.HibernateUtil;
import com.examscam.dao.*;
import java.util.Scanner;
import com.examscam.model.User;
public class AddUsers {
public static void main (String args[]) {
int keepAdding = 1;
while (keepAdding == 1) {
Scanner keyboard = new Scanner(System.in);
User user = new User();
System.out.println( "What is the user's login name? ");
user.setLoginName(keyboard.next());
System.out.println( "What is the email address? ");
user.setEmailAddress(keyboard.next());
System.out.println( "What is the password? ");
user.setPassword(keyboard.next());
}
}
}As you can see, the main method simply uses the Scanner class and the System.in stream to take input from the user, initializing the loginName, emailAddress, and password properties of a newly instantiated User instance.
Calling the DAO within a Transaction
Once the basic properties of the user instance are initialized, it's time to begin a transaction, create an instance of our DAO, and pass the User instance to the UserDAO's create method. That is the only database interaction needed for this program, so once the create method has finished executing, we ask the HibernateUtil class to commit the transaction.
user.setPassword(keyboard.next());
try {
HibernateUtil.beginTransaction();
UserDAO userDAO = new HibernateUserDAO();
userDAO.create(user);
HibernateUtil.commitTransaction();
System.out.println( "User successfully added. ");
} catch (HibernateException e) {
e.printStackTrace();
System.out.println( "Database Insert Failed ");
System.out.println(e.getClass() + e.getMessage());
}
System.out.println( "Would you like to continue? (1=y / 0=n) ");
keepAdding = keyboard.nextInt();
If the transaction commits successfully, we let the user know that the record was successfully added to the database. On the other hand, if a HibernateException is thrown, we must catch it, and tell the user that the insert has failed.
You'll also notice that the whole main method is insulated within a nice little while loop that keeps adding records to the database until the data entry person decides not to continue by entering a zero.
int keepAdding = 1;
while (keepAdding == 1) {
System.out.println( "Continue? ?(1=y / 0=n) ");
keepAdding = keyboard.nextInt();
}
The Whole AddUser Class
package com.examscam.app;
import java.util.Scanner;
import org.hibernate.HibernateException;
import com.examscam.HibernateUtil;
import com.examscam.dao.*;
import com.examscam.model.User;
public class AddUsers {
public static void main (String args[]) {
int keepAdding = 1;
while (keepAdding == 1) {
Scanner keyboard = new Scanner(System.in);
User user = new User();
System.out.println( "New user's login name? ");
user.setLoginName(keyboard.next());
System.out.println( "New user's email address? ");
user.setEmailAddress(keyboard.next());
System.out.println( "New user's password? ");
user.setPassword(keyboard.next());
try {
HibernateUtil.beginTransaction();
UserDAO userDAO = new HibernateUserDAO();
userDAO.create(user);
HibernateUtil.commitTransaction();
System.out.println( "User successfully added. ");
} catch (HibernateException e) {
e.printStackTrace();
System.out.println( "Database Insert Failed ");
System.out.println(e.getClass() + e.getMessage());
}
System.out.println( "Continue? (1=y / 0=n) ");
keepAdding = keyboard.nextInt();
}
}
}
Debriefing the AddUsers Class
Although the AddUsers class is intended as a very simple test case, there are a few important points to take out of how it uses the UserDAO.
First off, you should notice that while we are using a DAO, the developer writing the application is still tasked with the job of beginning and ending the database transaction. That's not an oversight in this application, but instead, a very typical programming construct.
In the AddUsers class, we only really hit the database once when inside the loop, which happens when we add a new user. But if there were multiple things going on, such as an update to one table, a delete from another, and a query to a third, all of which must happen as a single unit of work, then the developer using the DAO, or a multitude of DAOs for that matter, must be able to control when a transaction begins, when a transaction ends, and for that matter, which database centric components take part in that transaction. Transaction demarcation is not something you want to perform inside of a DAO, lest you fall into the 'one transaction per database interaction' anti-pattern. The developers using your DAO should be given the ability to start and end their own database transactions.
HibernateUtil.beginTransaction();
UserDAO userDAO = new HibernateUserDAO();
userDAO.create(user);
HibernateUtil.commitTransaction();
Now, in this case, the developer uses the HibernateUtil class to begin the transaction. It might have been kind to hide the call to the HibernateUtil's beginTransaction method within a wrapper method of the UserDAO, or the ExamScamDAO for that matter. I have no objections to that at all, so long as the DAO always knows what type of transaction mechanism is being used. However, depending upon where the DAO is deployed, the client may be using a different transaction mechanism than the one wrapped within the DAO. For example, in a J2EE environment, developers may want to do a JTA lookup to obtain a transactional context through the UserTransaction object, as opposed to going through your Hibernate code. In that case, using a DAO method that wraps the Hibernate Session's beginTransaction() method might not be the correct implementation strategy.
Much of this gets down to various important application design decisions, namely, how to make the transactional context available to the people using your data access objects. But regardless of what transactional context your applications use, it is important to emphasize that the developer or integrator using the DAOs will be tasked with beginning and ending their transactions in the same manner in which the HibernateUtil class is used in the main method of the AddUsers class.
Working Around the HibernateException
The other ugly aspect of the AddUsers class is the fact that the client application is forced to handle the HibernateException. The HibernateException is a runtime exception, and as such, the Java compiler does not force a developer to catch this exception in their code. If this exception is thrown at runtime, well, I guess it sucks to be you, but if you never catch it in your code, it's going to cause a runtime error. Here's how we handled the HibernateException in the AddUsers class:
try {
HibernateUtil.beginTransaction();
UserDAO userDAO = new HibernateUserDAO();
userDAO.create(user);
HibernateUtil.commitTransaction();
System.out.println( "User successfully added. ");
} catch (HibernateException e) {
/* Handle? Re-throw? Log? It's your call! */
e.printStackTrace();
System.out.println( "Database Insert Failed ");
System.out.println(e.getClass() +
e.getMessage());
}
Now, dealing with the HibernateException really isn't that big of a problem. If there's a database problem, I mean a real database problem, like the darn thing isn't plugged in, well, there's not much a stand-alone, JSF, or any other web based application can do about it other than provide a polite message to the end user. So, the applications using our DAOs should be wise enough to trap any exceptions that come to them, and respond appropriately.
How to handle the HibernateException is really another one of those application design questions on which development teams must come to a general agreement. I've seen some environments allow the HibernateException to bubble up to other application layers, and I've seen other development teams catch the HibernateException and rethrow it as a custom, checked application exception. Both strategies have merit, and more importantly, both approaches can work.
The Merits of Using DAOs
Overall, providing application developers or integrators Data Access Objects, as opposed to asking them to code directly against the Hibernate API, can greatly simplify application development, while at the same time, help to decouple and insulate changes that may occur in the database from other layers in an application. So long as DAO users know how to begin transactions, and handle the exceptions that might be thrown when accessing a DAO, interacting with the database layer will become easier, and long term maintainability of your application is greatly improved.
|