Java 1.5 Generic DAO
I am always looking for ways to limit the amount of typing I have to do. Hibernate helps me alot because I don't have to write all the SQL and I can concentrate on the less tedious aspects of designing my persistence model. Spring helps me alot because I don't have to write all those factories and other "glue" code.
In the interests of getting more done quickly, I started looking at how I can automate the generatin of the DAO pattern. Data Access Objects allow the service or session facade layer to be agnostic to the persistence layer used. As I have already said, I use hibernate but there might come a time when I need to use some other persistence layer and by keeping all the persistence layer specific code behind the DAO pattern, I can easily switch it out. I also love the fact that I can use jMock to test my service layer components without fooling around with actually changing database tables.
Back to automating the process of generating DAOs and their implementation. On one of my projects I am using AppFuse and it comes with a nifty tool to generate a full stack of components for managing persisted data. It can generate the classes either from a POJO or a database table. This is really nice but sometimes I don't want all of that. I have tables, and therefore pojos, that are not exposed past the service layer so I don't need the managers, controllers, and views that get generated by this tool. In this case, I really just want the DAO. I could use something like XDoclet, which BTW is what AppFuse's appgen uses, but Java 1.5 has added a new feature called Generics that I have been having lots of fun figuring out how to use. So here is what I did:
Firstly, I defined what I thought was the basic level of interface that all DAOs should provide. Essentially the CRUD methods:
01 package com.dhptech.dao; 02 03 import java.io.Serializable; 04 import java.util.List; 05 06 /** 07 * A generic DAO interface definition. This interface should be extended 08 * even if the new interface will add no additional functions. 09 * 10 * @author Dana P'Simer 11 * 12 * @param <T> The class of the pojo being persisted. 13 * @param <I> the class of the pojo's id property. 14 */ 15 public interface GenericDao<T,I extends Serializable> { 16 /** 17 * Get the object with the id specified. 18 * 19 * @param id the id of the instance to retrieve. 20 * 21 * @return the instance with the given id. 22 */ 23 T get(I id); 24 25 /** 26 * Get all instances that match the properties that are set in the given 27 * object using a standard Query by Example method. 28 * 29 * @param t the example bean 30 * 31 * @return a list of beans that match the example. 32 */ 33 List<T> get(T t); 34 35 /** 36 * Get all instances of this bean that have been persisted. 37 * 38 * @return a list of all instances. 39 */ 40 List<T> get(); 41 42 /** 43 * Persist the given bean. 44 * 45 * @param t the bean to persist. 46 */ 47 void save(T t); 48 49 /** 50 * Remove the bean with the given id. 51 * 52 * @param id the id of the bean to remove. 53 */ 54 void remove(I id); 55 56 /** 57 * Remove the bean passed. same as remove(t.<idProoertyGetter>()) 58 * 59 * @param t the object to remove. 60 */ 61 void remove(T t); 62 }
Here is the generic implementation:
001 /** 002 * 003 */ 004 package com.dhptech.dao.hibernate; 005 006 import java.io.Serializable; 007 import java.util.List; 008 009 import org.hibernate.HibernateException; 010 import org.hibernate.Session; 011 import org.hibernate.criterion.Example; 012 import org.hibernate.criterion.MatchMode; 013 import org.springframework.orm.hibernate3.HibernateCallback; 014 import org.springframework.orm.hibernate3.HibernateTemplate; 015 016 import com.dhptech.dao.GenericDao; 017 018 /** 019 * @author danap 020 * 021 */ 022 public abstract class GenericDaoHibernate<T,I extends Serializable> implements GenericDao<T,I> { 023 /** 024 * The hibernate template to use. 025 */ 026 private HibernateTemplate hibernateTemplate; 027 028 /** 029 * The class of the pojo being persisted. 030 */ 031 private Class<? extends T> clazz; 032 033 protected GenericDaoHibernate(Class<? extends T> clazz) { 034 this.clazz = clazz; 035 } 036 037 /** 038 * @return the hibernateTemplate 039 */ 040 public HibernateTemplate getHibernateTemplate() { 041 return hibernateTemplate; 042 } 043 044 /** 045 * @param hibernateTemplate the hibernateTemplate to set 046 */ 047 public void setHibernateTemplate(HibernateTemplate hibernateTemplate) { 048 this.hibernateTemplate = hibernateTemplate; 049 } 050 051 /** 052 * @return the clazz 053 */ 054 protected Class<? extends T> getClazz() { 055 return clazz; 056 } 057 058 /** 059 * @param clazz the clazz to set 060 */ 061 protected void setClazz(Class<? extends T> clazz) { 062 this.clazz = clazz; 063 } 064 065 /** 066 * @see com.dhptech.dao.GenericDao#get(java.io.Serializable) 067 */ 068 @SuppressWarnings("unchecked") 069 public T get(I id) { 070 return (T) getHibernateTemplate().load(clazz, id); 071 } 072 073 /** 074 * @see com.dhptech.dao.GenericDao#get(java.lang.Object) 075 */ 076 @SuppressWarnings("unchecked") 077 public List<T> get(final T t) { 078 if (t == null) { 079 return get(); 080 } else { 081 // filter on properties set in the customer 082 HibernateCallback callback = new HibernateCallback() { 083 public Object doInHibernate(Session session) throws HibernateException { 084 Example ex = Example.create(t).ignoreCase().enableLike(MatchMode.ANYWHERE); 085 return session.createCriteria(clazz).add(ex).list(); 086 } 087 }; 088 return (List<T>) getHibernateTemplate().execute(callback); 089 } 090 } 091 092 /** 093 * @see com.dhptech.dao.GenericDao#get() 094 */ 095 @SuppressWarnings("unchecked") 096 public List<T> get() { 097 return getHibernateTemplate().loadAll(clazz); 098 } 099 100 /** 101 * @see com.dhptech.dao.GenericDao#remove(java.io.Serializable) 102 */ 103 public void remove(I id) { 104 remove(get(id)); 105 } 106 107 /** 108 * @see com.dhptech.dao.GenericDao#remove(java.lang.Object) 109 */ 110 public void remove(T t) { 111 getHibernateTemplate().delete(t); 112 } 113 114 /** 115 * @see com.dhptech.dao.GenericDao#save(java.lang.Object) 116 */ 117 public void save(T t) { 118 getHibernateTemplate().save(t); 119 } 120 }
You will notice that this class is defined abstract and that it takes a Class extends T> object as an agument to the constructor. This is necessary because Generics are designed to assure type safeness through compile time checks. The rule is, if you have no "unchecked" warnings then you are type safe. At runtime there is no class specific information available. so you cannot have an expression like "T.class" to get the class of the parameter. Since Hibernate uses the class to lookup the mapping, for operations like the load function, we need to pass this in here. This also forces a caller to define a concrete class that extends this one in order to define the constructor that will pass in the Class object that is required. While one could get away without defining an subclassing interface for this pattern, I think it is a good practice to define both an interface and implemenation that extend these classes.
It would be simple to reimplement this generic implementation for other persistence models. Not sure if it could be done for a JDBC persistence framework but I know it can for Torque or iBatis.
Now all one has to do to use these is to define the specific DAO's interface and extend GenericDao, then create the specific DAO's hibernate implementation by extending the GenericDaoHibernate. Here is an example. Supose you have the following POJO:
01 package com.dhptech.dao.model; 02 03 /** 04 * @author danap 05 * 06 */ 07 public class Customer { 08 private Integer id; 09 private Integer version; 10 private String name; 11 private String contactName; 12 private String contactTelNum; 13 14 /** 15 * 16 */ 17 public Customer() { 18 // TODO Auto-generated constructor stub 19 } 20 21 /** 22 * @return the id 23 */ 24 public Integer getId() { 25 return id; 26 } 27 28 /** 29 * @param id the id to set 30 */ 31 public void setId(Integer id) { 32 this.id = id; 33 } 34 35 /** 36 * @return the version 37 */ 38 public Integer getVersion() { 39 return version; 40 } 41 42 /** 43 * @param version the version to set 44 */ 45 public void setVersion(Integer version) { 46 this.version = version; 47 } 48 49 /** 50 * @return the name 51 */ 52 public String getName() { 53 return name; 54 } 55 56 /** 57 * @param name the name to set 58 */ 59 public void setName(String name) { 60 this.name = name; 61 } 62 63 /** 64 * @return the contactName 65 */ 66 public String getContactName() { 67 return contactName; 68 } 69 70 /** 71 * @param contactName the contactName to set 72 */ 73 public void setContactName(String contactName) { 74 this.contactName = contactName; 75 } 76 77 /** 78 * @return the contactTelNum 79 */ 80 public String getContactTelNum() { 81 return contactTelNum; 82 } 83 84 /** 85 * @param contactTelNum the contactTelNum to set 86 */ 87 public void setContactTelNum(String contactTelNum) { 88 this.contactTelNum = contactTelNum; 89 } 90 }
To create a simple DAO for this object you could define the following interface:
01 package com.dhptech.dao; 02 03 import java.util.List; 04 05 import com.dhptech.dao.model.Customer; 06 07 /** 08 * @author danap 09 */ 10 public interface CustomerDao extends GenericDao<Customer,Integer> { 11 Customer getCustomerByName(String name); 12 List<Customer> searchCustomersByName(String name); 13 }
Now the hibernate implementation:
01 package com.dhptech.dao.hibernate; 02 03 import java.sql.SQLException; 04 import java.util.List; 05 06 import org.hibernate.HibernateException; 07 import org.hibernate.Session; 08 import org.hibernate.criterion.MatchMode; 09 import org.hibernate.criterion.Restrictions; 10 import org.springframework.dao.DataRetrievalFailureException; 11 import org.springframework.orm.hibernate3.HibernateCallback; 12 13 import com.dhptech.dao.CustomerDao; 14 import com.dhptech.dao.model.Customer; 15 16 /** 17 * @author danap 18 * 19 */ 20 public class CustomerDaoHibernate extends GenericDaoHibernate<Customer, Integer> implements CustomerDao { 21 /** 22 * The default constructor. 23 */ 24 public CustomerDaoHibernate() { 25 super(Customer.class); 26 } 27 28 /** 29 * @see com.dhptech.dao.CustomerDao#getCustomerByName(java.lang.String) 30 */ 31 public Customer getCustomerByName(String name) { 32 Customer ex = new Customer(); 33 ex.setName(name); 34 List<Customer> matches = get(ex); 35 if ( matches.size() == 0 ) { 36 throw new DataRetrievalFailureException("No customer found with the name, '"+name+"'"); 37 } else if ( matches.size() > 1 ) { 38 throw new DataRetrievalFailureException("Too many customers found with the name, '"+name+"'"); 39 } else { 40 return matches.get(0); 41 } 42 } 43 44 /** 45 * @see com.dhptech.dao.CustomerDao#searchCustomersByName(java.lang.String) 46 */ 47 @SuppressWarnings("unchecked") 48 public List<Customer> searchCustomersByName(final String name) { 49 return (List<Customer>) getHibernateTemplate().execute(new HibernateCallback() { 50 public Object doInHibernate(Session session) throws HibernateException, SQLException { 51 return session 52 .createCriteria(getClazz()) 53 .add(Restrictions.like("name", name, MatchMode.ANYWHERE)) 54 .list(); 55 } 56 }); 57 } 58 }
That is it.. Now I can enjoy the persistence layer independence of the DAO pattern without incurring the additional cost of actually coding one for each of my POJOs.
Addendum -- added 10/07/2006
Well it seems I was not the first person to think of this, of course. While I was looking at other implementations, I found that most had followed the basic design I have here. The Class object is passed into the generic base implementation because of the fact that the expression "T.class" is illegal. Well when I was reading hibernate.org's contribution to this pattern ( see "Hibernate's Generic DAO Pattern" ) and I saw some code that I thought was very interesting. It seems that it is possible to find the generic type arguments at runtime and get back a Class object for them. Here is the new constructor for the GenericDaoHibernate class:
01 /**
02 * The class of the pojo being persisted.
03 */
04 private Class<T> clazz;
05
06 protected GenericDaoHibernate() {
07 this.clazz = (Class<T>)
08 ((ParameterizedType)getClass().getGenericSuperclass())
09 .getActualTypeArguments()[0];
10 }
- danapsimer's blog
- 10141 reads


How about POJO Interface
Hi,
On the internet I see many examples of generic Hibernate dao's. Great! The examples I found use a class (Customer) to create the interface of the Hibernate dao to retrieve customers. In my code, I always use a interface for the pojo object and an implementation. So I want this code:
public interface CustomerDao extends GenericDao {
ICustomer getCustomerByName(String name);
List searchCustomersByName(String name);
}
The implementation should implement this CustomerDao interface and of course must know about the pojo class Customer (which implements ICustomer).
My question is: How can I create a generic interface for the Customer dao, which uses ICustomer objects in the interface, but let the implementation of this dao interface know about the pojo class it should use.
This is a good idea. I have
This is a good idea. I have been toying with it for months. I have just been developing the idea in a project I am working on and I plan to write a blog article about using Interface for your Model Objecs.
Post new comment