DRY CRUD DAOs with JPA
...and Spring
I wanted to shove a few more acronyms into the title of this article but I couldn't find anymore that were relevant. So I am currently working on a very small project using JPA and Hibernate. It has a very simple DAO layer and there are a handful of Model objects, POJO's, JavaBeans; whatever you young kids are calling them these days. Most everything in this application revolves around CRUD operations. So I created the following interface:
public interface BaseDAO<T> {
public void insert(T t);
public List<T> findAll(Class<T> objectClass);
public void delete(T t);
public void update(T t);
}
Now I can define any domain specific DAO interface I need without repeating CRUD methods (DRY) like so:
public interface ProjectDAO extends BaseDAO<Project> {
}
If there are any custom methods I need I can just define them in the ProjectDAO. If I find myself repeating the custom methods for every interface I can simply move them to the BaseDAO class. But it gets even easier. What about the impementations? Well, there is a base class for those as well.
public class JpaBaseDAO<T> extends JpaDaoSupport {
public void insert(T t) {
getJpaTemplate().persist(t);
}
public void delete(T t) {
getJpaTemplate().remove(t);
}
public void update(T t) {
getJpaTemplate().merge(t);
}
public List<T> findAll(Class<T> objectClass) {
List<T> entities = new ArrayList<T>();
try {
String s = "select c from " + objectClass.getSimpleName() + " c";
entities = getJpaTemplate().find(s);
} catch (Exception e) {
e.printStackTrace();
}
return entities;
}
}
Probably the most questionable method here is findAll (thanks to Will Hartung for suggesting this method). JPA doesn't provide a way to get all the rows from a table without specifiying a bit of HQL. To make it generic we just get the class name from the objectClass and use it. This does assume your tables are named the same as your domain objects. But like I said, this is a very small project and this isn't a problem for me. And now the implmentation:
public class JpaProjectDAO extends JpaBaseDAO<Project> {
}
I'm not sure how it could get much simpler. The nice thing is the base class and interface can be carried from project to project because they don't infer anything about the domain object. I'm sure folks are already doing this but I haven't seen a lot on line about it. Hope this helps someone. If anyone has any suggestions for improvements, especially concerning the findAll method, I'd love to hear them.
Re: DRY CRUD DAOs with JPA
In a previous version, prior to using Java 5, we had a code generator generate the repeated code, since it was markedly more difficult to implement without generics. That would also generate skeleton EJB session beans and all the crap required in EJB2. Now, with EJB3 and Java 5, we have so much less code.
Re: DRY CRUD DAOs with JPA
public class JpaBaseDAO<T> extends JpaDaoSupport {
private Class<T> persistentClass;
public JpaBaseDAO() {
this.persistentClass = (Class<T>)((ParameterizedType)getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
// etc
}
Then reference persistentClass whenever you need the class of the domain object. A little bit ugly, but it works :)
Re: DRY CRUD DAOs with JPA
Re: DRY CRUD DAOs with JPA
BaseDAO<T, PK extends Serializable>... and then the method looks like this:
public T findByPK(Class<T> objectClass, PK primaryKey) {
return entityManager.find(objectClass, primaryKey);
}
(Adapted to your example. In my version, I don't pass the objectClass into the findByPK method; instead I pass it in via the constructor).
I don't bother to subclass the BaseDAO unless I need to add methods.
Chris.
Re: DRY CRUD DAOs with JPA
Session hibernateSession = (Session) getEntityManager().getDelegate(); Criteria criteria = hibernateSession.createCriteria(objectClass); //Don't actually add any criteria return criteria.list()