So now, in slight contradiction to my "The Best Generic Dao Interface Ever" article, I am going to add Generics to the "AbstractHibernateDao".
If you don't know what Generics are, and you're a Java developer, you obviously aren't keeping up-to-date with your chosen trade. In fact, you should stop reading my blather and go study. Start here "New Features and Enhancements J2SE 5.0". Be sure to read the "Generics Tutorial"
Alrighty then. Why do we want generics on our DAO? Let's look back at our UserDao example, and have a look specifically at our "findAll" method...
public List<User> findAll() {
return all();
}
The problem with this method is in it's definition on the AbstractHibernateDao. The "all()" method comes from the AbstractHibernateDao and is defined like...
protected List all() {
return criteria().list();
}
Why are generics a problem here? Well the Hibernate critria returns a "List". It is a list of "Object"s, nothing more. Our AbstractHibernateDao respects that and returns a "List". This "List" is a "List<Object>" (a "List of objects"). Our UserDao on the other hand returns a "List<User>" (a "List of Users").
Well now we have a mismatch, a "List of Objects" is not a "List of Users", we are implying specifics that aren't enforced. Unfortunately there isn't much we can do about it. Hibernate doesn't have generics, so we have to have some faith that when we ask Hibernate for Users, it's going to give Users, not Toast. The easy fix for that is to mark our "UserDaoImpl.findAll()" method with the SuppressWarnings Annotation...
//fixin it the lazy way
@SuppressWarnings("unchecked")
public List findAll() {
return all();
}
That fixed it right? Wrong. This sucks. This sucks because I'll need to put this @SuppressWarnings annotation all over the place. I need it on each on all of the methods that return lists. I'll need it on most of the methods in every Dao I create. So like I said, this sucks.
OH! And don't forget about all the downcasting we're doing...
public User findByUsername(String username) {
return (User) criteria().add(
Restrictions.eq("username", username)
).uniqueResult();
}
Here, we are downcasting the "Object" returned from "uniqueResult()" to the "User" instance we asked for. Generics can help with all this.
My goal is for the DaoImpls to be "downcast" and "SuppressWarnings" free. In order to accomplish this I need to push the "dirty" stuff up into the AbstractHibernateDao. So I'll add a few wrapper methods that handle the downcasting and untyped collections...
public abstract class AbstractHibernateDao<E> {
private final Class<E> entityClass;
private final SessionFactory sessionFactory;
public AbstractHibernateDao(
Class<E> entityClass,
SessionFactory sessionFactory) {
Assert.notNull(entityClass,
"entityClass must not be null");
Assert.notNull(sessionFactory,
"sessionFactory must not be null");
this.entityClass = entityClass;
this.sessionFactory = sessionFactory;
}
protected Criteria criteria() {
return currentSession().createCriteria(entityClass);
}
protected Query query(String hql) {
return currentSession().createQuery(hql);
}
protected Session currentSession() {
return sessionFactory.getCurrentSession();
}
protected List<E> all() {
return list(criteria());
}
public Class<E> getEntityClass() {
return entityClass;
}
/*=== BEGIN GENERICS SUPPRESSION WRAPPERS ===*/
@SuppressWarnings("unchecked")
protected List<E> list(Criteria criteria) {
return criteria.list();
}
@SuppressWarnings("unchecked")
protected List<E> list(Query query) {
return query.list();
}
@SuppressWarnings("unchecked")
protected E uniqueResult(Criteria criteria) {
return (E) criteria.uniqueResult();
}
@SuppressWarnings("unchecked")
protected E uniqueResult(Query query) {
return (E) query.uniqueResult();
}
@SuppressWarnings("unchecked")
protected E get(Serializable id) {
return (E) currentSession().get(entityClass, id);
}
}
So, now, I've added generics to the AbstractHibernateDao. I've changed the class declaration to...
public abstract class AbstractHibernateDao<E>
Let's just jump right into the changes this makes to our UserDaoImpl...
public class UserDaoImpl extends AbstractHibernateDao<User> implements UserDao {
public UserDaoImpl(SessionFactory sessionFactory) {
super(User.class, sessionFactory);
}
public User findById(Long id) {
return get(id);
}
public User findByUsername(String username) {
return uniqueResult(criteria().add(
Restrictions.eq("username", username)
));
}
public List<User> findByEmail(String email) {
return list(query("from User u where u.email = :email")
.setParameter("email", email)
);
}
public List<User> findAll() {
return all();
}
public void save(User user) {
currentSession().saveOrUpdate(user);
}
public void delete(User user) {
currentSession().delete(user);
}
}
By employing Generics on the AbstractHibernateDao and isolating all downcasting and warning suppression to AbstractHibernateDao we can have a much cleaner DaoImpl. What I did was add wrapper methods to the AbstractHibernateDao for list(Criteria), list(Query), uniqueResult(Criteria), and uniqueResult(Query).
Using these new wrapper methods you see that the UserDaoImpl no longer calls query.list() it calls list(query) to get back a typed list. Also, the UserDaoImpl no longer calls criteria.uniqueResult(), it calls uniqueResult(criteria).
The isolation gives us one place to hide our dirty laundry (the AbstractHibernateDao). Maybe, some day, Hibernate will support Generics. That day is probably very, very far away. I would have thought the new JPA EntityManager API would support generics. Apparently it does not either, how unfortunate.