Thursday, July 26, 2012

Generic typesafe DAO with Guice and Hibernate

DAOs are an important part of many applications in the Java world. Most if not all entity classes will need a DAO for basic operations such as retrieval, persistence, updating and deletion. Writing a DAO for each and every entity can be a daunting task, not to mention that those operations will be duplicated among other DAOs.

Fortunately, there is an easy way to deal with this problem. It's possible to create a single DAO implementation which can be used to operate on any entity class. It is accomplished using Java Generics and Guice's TypeLiteral. For backwards compatibility with previous platforms, generic type information is erased during compilation, and therefore there's no way to determine the generic class at run-time. TypeLiteral tries to workaround this limitation allowing the generic type to be retained and queried at run-time. Basically, it works as a wrapper, and thus DAO bindings will look a bit different.

So, given the following generic DAO interface:

package com.example;

public interface GenericDao<T> {
  T get(int id);

  void save(T entity);

  void update(T entity);

  void delete(T entity);
}
and a Hibernate implementation named HibernateGenericDao<T>. Here's how the binding will look like for a User entity class:
bind(new TypeLiteral<GenericDao<User>>(){})
  .to(new TypeLiteral<HibernateGenericDao<User>>(){})
  .in(Scopes.SINGLETON);
Every entity class will need such a binding. Obviously, the User part will need to be replaced by the actual entity class name. Given the above binding, Guice will happily inject the generic class into HibernateGenericDao<T> class wrapped in a TypeLiteral object when it encounters TypeLiteral<T> in the constructor signature of the HibernateGenericDao<T> class. So here's the implementation:
package com.example;

import com.google.inject.Inject;
import com.google.inject.TypeLiteral;
import org.hibernate.Session;

public class HibernateGenericDao<T> implements GenericDao<T> {
  protected final Class<T> clazz;

  @Inject
  @SuppressWarnings("unchecked")
  public HibernateGenericDao(TypeLiteral<T> type) {
    this.clazz = (Class<T>) type.getRawType();
  }

  @Override
  @SuppressWarnings("unchecked")
  public T get(int id) {
    return (T) getSession().get(clazz, id);
  }

  @Override
  public void save(T entity) {
    getSession().persist(entity);
  }

  @Override
  public void update(T entity) {
    getSession().update(entity);
  }

  @Override
  public void delete(T entity) {
    getSession().delete(entity);
  }

  protected Session getSession() {
    HibernateUtil.getSessionFactory().getCurrentSession();
  }
}
If you're wondering about the HibernateUtil class, well, it's not in the Hibernate distribution. It is something that everyone writes his own in order to obtain the current Hibernate session. The above assumes that you will be using the one from the official Hibernate tutorial.

An example client that uses GenericDao<User>:

package com.example;

import com.google.inject.Inject;

class FooBar {
  private final GenericDao<User> userDao;

  @Inject
  public FooBar(GenericDao<User> userDao) {
    this.userDao = userDao;
  }

  public void doSomethingWithUser(int id) {
    User user = userDao.get(id);
    // do something with the user...
  }
}

Finally, here's how you would extend the HibernateGenericDao<T> class adding new methods specific to another entity:

package com.example;

import com.google.inject.Inject;

class ProductDao extends HibernateGenericDao<Product> {
  @Inject
  public ProductDao(TypeLiteral<Product> type) {
    super(type);
  }

  @SuppressWarnings("unchecked")
  public List<Product> findProducts(String search) {
    return getSession().createCriteria(Product.class)
      .add(Restrictions.like("name", "%" + search + "%")).list();
  }
}
Note that we don't need a binding for this class because clients will depend directly on this subclass rather than the generic interface, since they will need the Product specific methods too.

4 comments:

Anonymous said...

This is incredibly useful. I can't wait to try it out. Thanks a lot!

Anonymous said...

This is freaking great. Thank you.

Anonymous said...

thanks... this one helped me to understand proper transaction handling with daos and service-layer

Unknown said...

Thank you so much, this thing was driving me crazy :)

Post a Comment