Wednesday, September 26, 2007

Adding Generics to the AbstractHibernateDao

In my previous post, "The Best AbstractHibernateDao Ever", I made a passing reference to the generics being a problem.

So now, in slight contradiction to my "The Best Generic Dao Interface Ever" article, I am going to add Generics to the "AbstractHibernateDao".

If you don't know what Generics are, and you're a Java developer, you obviously aren't keeping up-to-date with your chosen trade. In fact, you should stop reading my blather and go study. Start here "New Features and Enhancements J2SE 5.0". Be sure to read the "Generics Tutorial"

Alrighty then. Why do we want generics on our DAO? Let's look back at our UserDao example, and have a look specifically at our "findAll" method...

public List<User> findAll() {
return all();
}

The problem with this method is in it's definition on the AbstractHibernateDao. The "all()" method comes from the AbstractHibernateDao and is defined like...

protected List all() {
return criteria().list();
}


Why are generics a problem here? Well the Hibernate critria returns a "List". It is a list of "Object"s, nothing more. Our AbstractHibernateDao respects that and returns a "List". This "List" is a "List<Object>" (a "List of objects"). Our UserDao on the other hand returns a "List<User>" (a "List of Users").

Well now we have a mismatch, a "List of Objects" is not a "List of Users", we are implying specifics that aren't enforced. Unfortunately there isn't much we can do about it. Hibernate doesn't have generics, so we have to have some faith that when we ask Hibernate for Users, it's going to give Users, not Toast. The easy fix for that is to mark our "UserDaoImpl.findAll()" method with the SuppressWarnings Annotation...

//fixin it the lazy way
@SuppressWarnings("unchecked")
public List findAll() {
return all();
}

That fixed it right? Wrong. This sucks. This sucks because I'll need to put this @SuppressWarnings annotation all over the place. I need it on each on all of the methods that return lists. I'll need it on most of the methods in every Dao I create. So like I said, this sucks.

OH! And don't forget about all the downcasting we're doing...

public User findByUsername(String username) {
return (User) criteria().add(
Restrictions.eq("username", username)
).uniqueResult();
}

Here, we are downcasting the "Object" returned from "uniqueResult()" to the "User" instance we asked for. Generics can help with all this.

My goal is for the DaoImpls to be "downcast" and "SuppressWarnings" free. In order to accomplish this I need to push the "dirty" stuff up into the AbstractHibernateDao. So I'll add a few wrapper methods that handle the downcasting and untyped collections...

public abstract class AbstractHibernateDao<E> {

private final Class<E> entityClass;
private final SessionFactory sessionFactory;

public AbstractHibernateDao(
Class<E> entityClass,
SessionFactory sessionFactory) {

Assert.notNull(entityClass,
"entityClass must not be null");
Assert.notNull(sessionFactory,
"sessionFactory must not be null");

this.entityClass = entityClass;
this.sessionFactory = sessionFactory;
}

protected Criteria criteria() {
return currentSession().createCriteria(entityClass);
}

protected Query query(String hql) {
return currentSession().createQuery(hql);
}

protected Session currentSession() {
return sessionFactory.getCurrentSession();
}

protected List<E> all() {
return list(criteria());
}

public Class<E> getEntityClass() {
return entityClass;
}

/*=== BEGIN GENERICS SUPPRESSION WRAPPERS ===*/

@SuppressWarnings("unchecked")
protected List<E> list(Criteria criteria) {
return criteria.list();
}

@SuppressWarnings("unchecked")
protected List<E> list(Query query) {
return query.list();
}

@SuppressWarnings("unchecked")
protected E uniqueResult(Criteria criteria) {
return (E) criteria.uniqueResult();
}

@SuppressWarnings("unchecked")
protected E uniqueResult(Query query) {
return (E) query.uniqueResult();
}

@SuppressWarnings("unchecked")
protected E get(Serializable id) {
return (E) currentSession().get(entityClass, id);
}
}


So, now, I've added generics to the AbstractHibernateDao. I've changed the class declaration to...
public abstract class AbstractHibernateDao<E>

Let's just jump right into the changes this makes to our UserDaoImpl...

public class UserDaoImpl extends AbstractHibernateDao<User> implements UserDao {

public UserDaoImpl(SessionFactory sessionFactory) {
super(User.class, sessionFactory);
}

public User findById(Long id) {
return get(id);
}

public User findByUsername(String username) {
return uniqueResult(criteria().add(
Restrictions.eq("username", username)
));
}

public List<User> findByEmail(String email) {
return list(query("from User u where u.email = :email")
.setParameter("email", email)
);
}

public List<User> findAll() {
return all();
}

public void save(User user) {
currentSession().saveOrUpdate(user);
}

public void delete(User user) {
currentSession().delete(user);
}
}

By employing Generics on the AbstractHibernateDao and isolating all downcasting and warning suppression to AbstractHibernateDao we can have a much cleaner DaoImpl. What I did was add wrapper methods to the AbstractHibernateDao for list(Criteria), list(Query), uniqueResult(Criteria), and uniqueResult(Query).

Using these new wrapper methods you see that the UserDaoImpl no longer calls query.list() it calls list(query) to get back a typed list. Also, the UserDaoImpl no longer calls criteria.uniqueResult(), it calls uniqueResult(criteria).

The isolation gives us one place to hide our dirty laundry (the AbstractHibernateDao). Maybe, some day, Hibernate will support Generics. That day is probably very, very far away. I would have thought the new JPA EntityManager API would support generics. Apparently it does not either, how unfortunate.

17 comments:

xavzeman said...

Hello and thanks for your contribution. I have the following follow-up questions for you:

1/ isn't it something missing in the concrete class declaration:
public class UserDaoImpl extends AbstractHibernateDao[User] implements UserDao

You changed the abstract class declaration to public abstract class AbstractHibernateDao[E, I extends Serializable]
from public abstract class AbstractHibernateDao[E] but you don't reflect that change in the subclass of UserDaoImpl.

2/ Why exactly do you need to add the I extends Serializable part at all?

3/ In the AbstractHibernateDao class, you simply do import of the following:
import org.hibernate.Criteria;
import org.hibernate.Query;
import org.hibernate.Session;
import org.hibernate.SessionFactory;

Is that enough to be sure you are compliant with Hibernate3? so far, I used to have import org.springframework.orm.hibernate3 for that.

4/There is a type for the last method in the abstract class, it should read:
public Class[E] getEntityClass() {

Thanks and keep up this great blog.

PS: I replace < with [ cause it causes problem when posting
Xman

Ray Krueger said...

Copying and pasting from the IDE to the blog only goes so far :)

1) Good Catch. The UserDaoImpl has been updated above to have the proper generics.

2) I declare that the I parameter "extends Serializable" because the Hibernate Session API requires Serializable types for it's get() and load() methods.

3) The point of my HibernateDao implementations here is that it is not using the spring.orm.hibernate3 package. Have a look at the previous article "The Best AbstractHibernateDao Ever". You'll see I mention that the HibernateTemplate is no longer required (unless you want the DataAccessException translation functionality. See Alef Arendsen's post, "So should you still use Spring's HibernateTemplate and/or JpaTemplate??".

4) Again! Good Catch :) I have fixed this declaration.

Thanks for commenting! I'm glad someone finds my babble interesting enough to read :)

xavzeman said...

Also in a previous post you wrote "If you are using the Spring LocalSessionFactoryBean to configure your Hibernate SessionFactory, it just isn't needed."

I understand the reason why if you using Hibernate3 feature of contextuel session like you do, but my question is how do you load your .hbm.xml files now or define your hibernate properties for that matter? How do you associate this with the contextual session?

For instance if I used to have the following in my application context,

[bean id="mySessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"]
[property name="dataSource" ref="myDataSource" /]
[property name="mappingResources"]
[list]
[value]MyBean.hbm.xml[/value]
[/list]
[/property]
[property name="hibernateProperties"]
[value]
hibernate.dialect=org.hibernate.dialect.HSQLDialect
[/value]
[/property]
[/bean]

Ray Krueger said...

That was just really poor wording on my part. I have corrected it to read "If you are using the Spring LocalSessionFactoryBean to configure your Hibernate SessionFactory, the HibernateTemplate just isn't needed."

What you have is absolutely correct.

xavzeman said...

Glad I could help you in return. And like I said before, keep up this absolutely useful blog even if i am the only one commenting :)

xavzeman said...

Without the HibernateTemplate, we are losing some convenient method like deleteAll or saveOrUpdateAll for Collections.

I was wondering a contextual session
handles collections, if I pass a collection inside the saveOrUpdate method. (After all, a collection is an object). I certainly hope that they identify the type being a collection and optimize the query.

Basically, if you had a list of Users to save all at once, how would you implement it in your context?

By the way, I also read in the springframework reference the following: "all references to the org.hibernate package need to be replaced with net.sf.hibernate, following the root package change in Hibernate 3." I assume it means that you better not use import like org.hibernate.Criteria or org.hibernate.Query anymore but you should use net.sf.hibernate.Criteria...

Do you understand the same ?

Ray Krueger said...

Actually the HibernateTemplate implements the saveOrUpdateAll by iterating over the given collection and calling session.save.
HibernateTemplate

There isn't much to optimize in regards to sql inserts and updates. Each record to be inserted/updated requires it's own statement. They statements won't be executed until your transaction commits.

That being said...
We have a "save" method on UserDaoImpl, we could implement saveAll the same way...

public void saveAll(Collection<User> users) {
 for (User user : users) {
  save(user);
 }
}


As for the packages, I think you may have read that wrong. Hibernate3 uses "org.hibernate", the old hibernate 2x stuff used "net.sf.hibernate".

xavzeman said...

Hello,

Quick little question, with all this abstract layer and with your experience, what do you think is the best way to deal with java.util.Date fields of a model within Hibernate so that the code still works even if you switch database (mySQL,postGresSql...).

I read some stuff about it but nothing convincing, I was curious how do you deal with Date fields.

Ray Krueger said...

You just use java.util.Date instances.

In your mappings you tell hibernate if you want that property mapped as a DATE, TIME, or TIMESTAMP.

Have a look at the Hibernate Reference

xavzeman said...

I did. I even posted a post on hibernate.org. See below. If I use "DATETIME" or "DATE" in the hbm.xml instead of java.util.Date, I get an error stating like that:

Could not determine type for: DATETIME, for columns:

===POST BELOW====

The java.util.Date and the java.sql.Date within the hibernate context is one of the most often asked question.
It is without a doubt the most confusing. I am creating this thread hoping to gather all in one place the
answers to this question.

Context:
a model has member variables using a java.util.Date
Date myDateOnly;
Date myDateWithTime;

the hbm.xml is defined as:
[property name="myDateOnly" column="my_date_only" type="java.util.Date" /]
[property name="myDateWithTime" column="my_date_with_time" type="java.util.Date" /]


the database underneath declares the type as:
my_date_only column is created as a DATE (mySQL mode)
my_date_only column is created as a DATETIME (mySQL mode)

Execution:
SimpleDateFormat dateFormatter = new SimpleDateFormat("yyyy-mm-dd");
Date dateOnly = dateFormatter.parse("2007/10/23");
Date dateWithTime = Calendar.getInstance().getTime();

model.setMyDateOnly(dateOnly);
model.setMyDateWithTime(dateWithTime);
model.save();

//imagine code to get a model back from db.
dateOnlyFromDb = model.getMyDateOnly();
dateWithTimeFromDb = model.getMyDateWithTime();

assertTrue(dateOnly.equals(dateOnlyFromDb));
assertTrue(dateWithTime.equals(dateWithTimeFromDb));

Now the question:
Both asserts fail. Why?

Ray Krueger said...

I'm posting on your comment at http://forum.hibernate.org/viewtopic.php?t=980652

...and DATETIME isn't supported hibernate type, it's "TIME". That's why you're getting that error.

Anonymous said...

Hello,

I am new in Spring framework. I want some explanation for rolling back from failed insert.

In my case, let say that we have 5 items to be saved in persistence. If insert-attempt is failed in the middle, I have to rollback all transactions that have been done. How to make it happen?

These are my ApplicationContext-hibernate.xml:
bean id="msUserDao"
class=".....MsUserDaoImpl"
parent="abstractHibernateDao"

Thanks for the explanation.

Regards,
LSH

Ray Krueger said...

That is too large of a topic to be covered in a blog comment. You should read the reference documentation at http://static.springframework.org/spring/docs/2.5.x/reference/index.html, or maybe look at buying a book.

You could try posting on the forums, but again you should probably do some studying on your own first.
-Ray

vij said...

Hi,

I tried using your abstractHibernateDao. It works perfectly well in container-managed situation.. But, when I am not using any container, it throws an exception "org.hibernate.HibernateException: No Hibernate Session bound to thread, and conf
iguration does not allow creation of non-transactional one here at org.springframework.orm.hibernate3.SpringSessionContext.currentSessio
n(SpringSessionContext.java:63)".

I am using AnnotationSessionFactoryBean for sessionfactory and org.springframework.orm.hibernate3.HibernateTransactionManager as transactionManager.

Not able to figure out If I need to do anything extra in a non-container situation.

Ray Krueger said...

I really don't know what you mean by container-managed or not. Either way, I'll try to address your question.

You've got a basic Spring/Hibernate configuration problem. Even if you were using the old Spring HibernateTemplate you'd get the same error. You have no transaction or hibernate interceptors in the path before the call to currentSession() that tell Spring to create a Hibernate session.

Spring won't create a hibernate session on the fly, it expects an active one in the current thread as created by an interceptor.

Anonymous said...

Thanks for the response. I did dig in some more and found that it has nothing to do with non-container ..

I'll explain the situation:

1) For Services which were writing into database, I annotated them with @transactional. For the ones , which just read, I omitted the annotation

2) The error occurs when I call these services which doesn't have annotation as it is clear that there is no transaction and so the exception is proper.

2) However, this worked while using HibernateDaoSupport because, hibernate template gets session from sessionfactoryutils, which exposes an option to create a session even if a transaction doesn;t exist.getSession(SessionFactory,allowCreate)


4) Now my question is , Is it fine if i change the code in our AbstarctHibernateDao to get Current Session from sessionFactory.getCurrentSession() to SessionFactoryUtils.getSession(sessionFactory,true)

It works . But I dont know if it would create any side effects

Ray Krueger said...

I've always annotated the class as readonly, and made specific methods not-read only.

Creating sessions on the fly can be dangerous if bad code creeps in to your application that causes sessions to be created in the wrong places.