Saturday, January 31, 2009

Hibernate DAO using Spring and Java Generics

Having created a number of Hibernate DAOs it does not take long to see a pattern emerge which would seem to lend itself to a typed generic DAO. As any good engineer will do, I first set off to see if someone else had already thought about this. It is sometimes easier to stand on the shoulders of giants, and I found one.

The work I am going to present was really inspired by the following blog posting:

http://www.nileshk.com/node/66

But I wanted to change the implementation somewhat, and add more methods that made sense to my problem domain. So what I am going to present is an extension to the above blog posting. The purpose of this is not to suggest that one implementation is better or worse, they are just different and I actually hope that perhaps my implementation will inspire someone else in their work.

The AbstractDao interface looks like the following - with nearly every comment removed for clarity. I hope the method names are suggestive enough:

package com.redpointtech.dao;

import java.util.Collection;
import java.util.List;

/**
* borrowed heavily from: http://www.nileshk.com/node/66
*/
public interface AbstractDao <DomainObject, KeyType> {

void update(DomainObject object);
void refresh(DomainObject object);
void save(DomainObject object);
Long saveOrUpdate(DomainObject object);
void saveAll(Collection<DomainObject> objects);


DomainObject load(KeyType id);
List<DomainObject> findAll();
List<DomainObject> findAllOrderById();
DomainObject findById(KeyType id);
List<DomainObject> findByIds(Collection<KeyType> idList);
List<DomainObject> findAllOrderByProp(String propertyName, Boolean asc);


int deleteAll();
void deleteAll(Collection<DomainObject> objects);
void delete(DomainObject object);
void deleteById(KeyType id);
int deleteByIds(Collection<KeyType> ids);

int count();

}



The Implementation looks as follows:

package com.redpointtech.dao;

import static org.springframework.util.CollectionUtils.isEmpty;

import java.io.Serializable;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.EntityMode;
import org.hibernate.Query;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.metadata.ClassMetadata;
import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
import org.springframework.util.Assert;

/**
*
* borrowed heavily from: http://www.nileshk.com/node/66
*/
public abstract class AbstractHibernateDaoImpl<T extends Serializable, KeyType extends Serializable>
extends HibernateDaoSupport implements AbstractDao<T, KeyType>{

@SuppressWarnings("unchecked")
protected Class<T> domainClass = getDomainClass();

public void refresh(T t) {
getHibernateTemplate().refresh(t);
}

@SuppressWarnings("unchecked")
public T load(KeyType id) {
return (T) getHibernateTemplate().load(domainClass, id);
}

@SuppressWarnings("unchecked")
public T findById(KeyType id) {
return (T) getHibernateTemplate().get(domainClass, id);
}

@SuppressWarnings("unchecked")
public List<T> findByIds(Collection<KeyType> idList) {
Assert.notNull(idList, "idList parameter cannot be null");
ClassMetadata cmd = getHibernateTemplate().getSessionFactory().getClassMetadata(domainClass);
String idProp = cmd.getIdentifierPropertyName();

if( idList.size() > 0 ) {
Criteria criteria = getHibernateTemplate().getSessionFactory()
.getCurrentSession().createCriteria(domainClass);
criteria.add(Restrictions.in(idProp, idList));

return criteria.list();
} else {
return new ArrayList<T>();
}
}

public void update(T t) {
getHibernateTemplate().update(t);
}

public void save(T t) {
getHibernateTemplate().saveOrUpdate(t);
}

public Long saveOrUpdate(T t) {
getHibernateTemplate().saveOrUpdate(t);
Long id = (Long)getHibernateTemplate().getSessionFactory().getClassMetadata(domainClass).getIdentifier(t, EntityMode.POJO);
return id;
}

public void saveAll(Collection<T> list) {
if(!isEmpty(list)) {
getHibernateTemplate().saveOrUpdateAll(list);
}
}

public void delete(T t) {
getHibernateTemplate().delete(t);
}

@SuppressWarnings("unchecked")
public List<T> findAll() {
return (getHibernateTemplate().find("from " + domainClass.getName()
+ " x"));
}

@SuppressWarnings("unchecked")
public List<T> findAllOrderById() {
ClassMetadata cmd = getHibernateTemplate().getSessionFactory().getClassMetadata(domainClass);
String idProp = cmd.getIdentifierPropertyName();

return findAllOrderByProp(idProp,true);
}

@SuppressWarnings("unchecked")
public List<T> findAllOrderByProp(String propertyName, Boolean asc) {

Criteria criteria = getHibernateTemplate().getSessionFactory()
.getCurrentSession().createCriteria(domainClass);
if( asc ) {
criteria.addOrder(Order.asc(propertyName));
} else {
criteria.addOrder(Order.desc(propertyName));
}


return criteria.list();
}


public void deleteById(KeyType id) {
Object obj = load(id);
getHibernateTemplate().delete(obj);
}

@SuppressWarnings("unchecked")
public int deleteByIds(Collection<KeyType> ids) {
int count = 0;

if(!isEmpty(ids)) {

ClassMetadata cmd = getHibernateTemplate().getSessionFactory().getClassMetadata(domainClass);
String idProp = cmd.getIdentifierPropertyName();

String hqlDelete = "delete " + domainClass.getName() + " x where x." + idProp + " in ( :ids )";

Query deleteQuery = getHibernateTemplate().getSessionFactory().getCurrentSession().createQuery(hqlDelete);
deleteQuery.setParameterList("ids", ids);

count = deleteQuery.executeUpdate();

}
return count;
}

public void deleteAll(Collection<T> objects) {
if(!isEmpty(objects)) {
getHibernateTemplate().deleteAll(objects);
}
}

public int deleteAll() {

String hqlDelete = "delete " + domainClass.getName();
Query deleteQuery = getHibernateTemplate().getSessionFactory().getCurrentSession().createQuery(hqlDelete);

int count = deleteQuery.executeUpdate();
return count;
}

public int count() {
ClassMetadata cmd = getHibernateTemplate().getSessionFactory().getClassMetadata(domainClass);
String idProp = cmd.getIdentifierPropertyName();

Criteria criteria = getHibernateTemplate().getSessionFactory()
.getCurrentSession().createCriteria(domainClass);
criteria.setProjection(Projections.countDistinct(idProp));
Object countValue = criteria.uniqueResult();

return Integer.parseInt(countValue.toString());
}

protected Class getDomainClass() {
if (domainClass == null) {
ParameterizedType thisType = (ParameterizedType) getClass().getGenericSuperclass();
domainClass = (Class) thisType.getActualTypeArguments()[0];
}
return domainClass;
}

}



I incorporated this into my Redpoint Notes reference project and I will show how that looks for the DAO hierarchy.

NotesDao
package com.redpointtech.dao;

import java.util.List;

import com.redpointtech.domain.Note;

public interface NotesDao {

List<Note> getAllNotes();

Long saveOrUpdate(Note note);

}



HibernateNotesDao Interface
package com.redpointtech.dao;

import com.redpointtech.domain.Note;

public interface HibernateNotesDao extends NotesDao,
AbstractDao<Note, Long> {

}



HibernateNotesDaoImpl
package com.redpointtech.dao.impl;

import java.util.List;
import java.util.Map;

import org.springframework.stereotype.Repository;

import com.redpointtech.dao.AbstractHibernateDaoImpl;
import com.redpointtech.dao.HibernateNotesDao;
import com.redpointtech.domain.Note;

@Repository("hibernateNotesDao")
public class HibernateNotesDaoImpl extends AbstractHibernateDaoImpl<Note, Long>
implements HibernateNotesDao {



@Override
public List<Note> getAllNotes() {
List<Note> notes = this.findAll();
return notes;
}


}



As you can see I wrote virtually no code beyond what the abstract generic implementation gives me - and the getAllNotes is really contrived just to show how you would add a specific DAO method.

Using this kind of generic Hibernate DAO has saved us a lot of time and I hope you find this useful as well.

1 comment:

nabil said...

It don't work form me a have this error in console :

Exception in thread "main" java.lang.NullPointerException
at ma.athic.aft.newdao.AbstractHibernateDaoImpl.findAll(AbstractHibernateDaoImpl.java:89)
at ma.athic.aft.newdao.HibernateDossierDaoImpl.getAllDossier(HibernateDossierDaoImpl.java:24)
at ma.athic.aft.newdao.TestArchitecture.main(TestArchitecture.java:8)


Please can you send your config file