Generic DAO with Hibernate and Spring, is their a better way than this?
Solution 1
I think your solution is fine but you do not need the class parameter T
. It just limits you and does not allow to re-use the same DAO for Integers and Strings (for example).
Save method does not need this type at all.
Methods like get()
or find()
should receive generic type themselves:
public <T> T findById(Class<T> clazz, Serializable id);
public <T> List<T> listAll( Class<T> clazz );
Solution 2
A good place to start is this Generic DAO article its from 2006 but has some good information in it. To update the generic DAO for Spring, hibernate and annotations this is what I have done. Also this newer article is quite useful as well.
All identifier is is a generic intferface to make sure the class has a I getId()
and setId(I id)
Create a Generic DAO interface
public interface GenericDao<T extends Identifier<I>, I extends Serializable> {
public T find(I id);
public void delete(T obj);
public void saveOrUpdate(T obj);
}
Create your GenericDAO Implementation
public abstract class GenericDaoImpl<T extends Identifier<I>, I extends Serializable> implements GenericDao<T, I>{
private Class<T> type;
@Autowired
private SessionFactory sessionFactory;
public void setSessionFactory(SessionFactory sessionFactory) {
this.sessionFactory = sessionFactory;
}
protected SessionFactory getSessionFactory() {
if (sessionFactory == null)
throw new IllegalStateException("SessionFactory has not been set on DAO before usage");
return sessionFactory;
}
public Class<T> getType() {
return type;
}
public GenericDaoImpl() {
this.type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
}
@Transactional(readOnly = true)
@Override
public T find(I id) {
return (T) getSessionFactory().getCurrentSession().get(getType(), id);
}
@Transactional
@Override
public void delete(T obj) {
getSessionFactory().getCurrentSession().delete(obj);
}
@Transactional
@Override
public void saveOrUpdate(T obj) {
getSessionFactory().getCurrentSession().saveOrUpdate(obj);
}
}
Object DAO interface:
public interface SomeObjectDao extends GenericDao<SomeObject, Long>{
}
Object DAO implementation
@Repository
public class SomeObjectDaoImpl extends GenericDaoImpl<SomeObject, Long> implements SomeObjectDao {
}
now in Any class that needs it, like a service class, you can get autowiring by just adding the object class dao that you need
@Autowired
private SomeObjectDao someObjectDao;
Solution 3
Better than writing it by yourself would be using
- Spring-Data-JPA or
- Hades (it is the somehow the predecessor of Spring Data Jpa)
In both it will look very similar: only the interface, no Implmentation:
public interface UserDao extends GenericDao<User, Long> {
User findByLogin(String login);
}
If you are interested in, look at the documentation.
Solution 4
It looks like you are passing the type to the dao just so you can get the correct type for the generics in the dao. Rather than do that you could use Spring's java configuration which would allow you to have a method something like:
@Bean(name="myAsdfDao")
public GenericDao<MyAsdf, MyAsdfId> getMyAsdfDao() {
return new GenericDao<MyAsdf, MyAsdfId>();
}
which would let you keep entity specific daos without having to pass the type to a constructor through xml config. This would be in an @Configuration annotated class that provides java based configuration for spring.
NimChimpsky
side hustle : metriculous.network Spring is too bloated, I created my own web app framework Infrequent tweets What if programming languages were methods to eat an orange?
Updated on June 04, 2022Comments
-
NimChimpsky almost 2 years
public class GenericDao <T, PK extends Serializable> { private final Class<T> type; @Resource(name = "sessionFactory") private SessionFactory sessionFactory; public GenericDao(final Class<T> type) { this.type = type; } public PK save(final T o) { return (PK) sessionFactory.getCurrentSession().save(o); } // ... get,delete, etc
App context bean:
<bean id="fooDao" class="com.mycompany.dao.GenericDao"> <constructor-arg> <value>com.mycompany.Foo</value> </constructor-arg> </bean>
And in service layer invoke like so :
@Autowired private GenericDao<Foo, Integer> fooDao; ... public doStuffIncludingSave(Foo foo) fooDao.save(foo);
-
NimChimpsky about 12 yearsand then I could remove the constructor argument and use the same implemtation to find/delete any type ?
-
NimChimpsky about 12 yearsand how would I invoke with dependency injection ?
-
NimChimpsky about 12 yearswhat would it look like in either of them ?
-
NimChimpsky about 12 yearsaha, so I would have different methods on the class for each entity type (foo, bar etc).
-
Glenn Lawrence over 10 yearsWhich class is your Identifier? e.g. org.springframework.expression.spel.ast.Identifier or org.hibernate.metamodel.relational.Identifier. Either way I get a compile error saying: "Identifier does not take parameters"
-
Sebastien about 10 yearsNeither is what I used, Made it myself:
public interface Identifier<I extends Serializable> { I getId(); void setId(I identifier); }
-
Dev about 9 yearsvary helpful answer @Sebastien
-
Ayman Al-Absi almost 9 yearsThis implementation does not have Service interface and implementation. To let it work without Synchronization error, make sure to change
@EnableTransactionManagement
in Configuration class to@EnableTransactionManagement(proxyTargetClass=true)
. Then move@Transactional
annotation to Service Implementation class instead of DaoImpl Class which already annotated with@Repository
-
Erlan over 8 yearsIt would be nice if there was short explanation of what it it does?