use AOP to simplify hibernateTemplate (Hibernate3)
Spring ORM 替 Hibernate 的 API (Session and Query) 做了一番修整,也就是 HibernateTemplate ,它最主要的目的是 resource 控管及 exception 的轉換。有了這個 persistence layer 的 code 就可以大量的減少並且降低出錯的機會。然而這個 API 卻抹剎了 Hibernate API 的簡單易用優點,舉例來說,如果藉由 hibernateTemplate 控制 Hibernate Session (ex. Criteria),唯一的做法就是寫很難看的 Callback:
public MyData findByNo(final String no) {
final String hql = " select data from MyData data "
+ " where data.no = :no ";
HibernateCallback hc = new HibernateCallback() {
public Object doInHibernate(Session session) {
Query q = session.createQuery(hql);
q.setString("no", no);
return q.uniqueResult();
}
};
return (MyData) hibernateTemplate.execute(hc);
}
inner class + final.... 有夠難看。下面要介紹的是,利用 Spring AOP 的 AutoProxy 的功能,自動將 DAO 的所有 method 用 HibernateCallback 包起來。如此一來,便可以自由在 DAO 中自由存取 Hibernate Session 了:
總共需要三個 class:
第一個是 Advice,也就是將所有 method 都先用 HibernateCallback 包起來,然後將 Hibernate Session 放入 HibernateDAOWarpper 裡。
接下來就是 AutoProxyCreator,當它遇到 bean 的 class 為 HibernateDAOWrapper 的 subclass 時,它就會自動替該 bean 做 proxy。
public class HibernateDAOWrapperMethodInterceptor
implements MethodInterceptor {
private HibernateTemplate hibernateTemplate;
public Object invoke(final MethodInvocation methodInvocation)
throws Throwable {
try {
return hibernateTemplate.execute(new HibernateCallback() {
public Object doInHibernate(Session session)
throws HibernateException, SQLException {
HibernateDAOWrapper.currentSession.set(session);
try {
return methodInvocation.proceed();
} catch (Throwable e) {
if (e instanceof HibernateException) {
throw (HibernateException) e;
} else if (e instanceof SQLException) {
throw (SQLException) e;
} else {
//must re-throw as RuntimeException, and let
//hibernateTemplate deal with resource management
throw new ExceptionCarrier(e);
}
}
}
});
} catch (ExceptionCarrier e) {
throw e.throwable;
}
}
private static class ExceptionCarrier extends RuntimeException {
private Throwable throwable;
public ExceptionCarrier(Throwable t) {
this.throwable = t;
}
}
public void setHibernateTemplate(HibernateTemplate hibernateTemplate) {
this.hibernateTemplate = hibernateTemplate;
}
}
public class HibernateDAOWrapperAutoProxyCreator extends
AbstractAutoProxyCreator {
protected Object[] getAdvicesAndAdvisorsForBean(Class beanClass,
String beanName, TargetSource customTargetSource)
throws BeansException {
if (HibernateDAOWrapper.class.isAssignableFrom(beanClass)) {
return PROXY_WITHOUT_ADDITIONAL_INTERCEPTORS;
} else {
return DO_NOT_PROXY;
}
}
}
有了這個,再搭配上面的 Advice,我們就可以將所有 HibernateDAOWrapper 的 subclass 的所有 method 都用 HibernateCallback 包起來。Spring 的設定如下:
<bean id="hibernateDAOWrapperAutoProxyCreator"
class="org.bioinfo.util.spring.dao.HibernateDAOWrapperAutoProxyCreator">
<property name="interceptorNames">
<list>
<value>hibernateDAOWrapperMethodInterceptor</value>
</list>
</property>
<!-- for use CGlib to create proxy,
(default to false, which will use Proxy if it detects interface presence) -->
<property name="proxyTargetClass">
<value>true</value>
</property>
</bean>
<bean id="hibernateDAOWrapperMethodInterceptor"
class="org.bioinfo.util.spring.dao.HibernateDAOWrapperMethodInterceptor">
<property name="hibernateTemplate">
<ref local="hibernateTemplate"/>
</property>
</bean>
這個只要設一次就夠了。接下來我們再來看看主角:
/**
* A special HibernateTemplate wrapper for DAO. You need to do:
*
* (1) Extends this class and use getSession() to obtain Hibernate3 Session
*
* <code>
* public class YourDAO extends HibernateDAOWrapper {
*
* public MyProject findByProjectCode(String code) {
* String hql = "select prj from MyProject prj where prj.code = :code";
*
* return (MyProject) getSession().createQuery(hql)
* .setString("code",code").uniqueResult();
* }
* }
* </code>
*
* (2) At your spring *.xml, just write one line:
*
* <code>
* <bean id="yourDAO" class="some.thing.YourDAO"></bean>
* </code>
*
* done !!
* @author ingram
*
*/
public abstract class HibernateDAOWrapper {
final static ThreadLocal currentSession = new ThreadLocal();
protected final Session getSession() {
return (Session) currentSession.get();
}
}
呵,小的可憐,它的功能就是提供 getSession() 的 method 給 subclass 的 DAO 而已。這個 session 來自於 Advice,並且放在 ThreadLocal裡,因此是 thread-safe 的。使用方式就如上面的 javadoc 所說,將 DAO 直接 subclass 這個 Wrapper,並在 spring 裡定義即可。
這樣一來 DAO 就可以快快樂樂的用 Hibernate API 啦,爽!
(註:僅適用 Hibernate3,於 Hibernate 3.0.3 + Spring 1.2 final 測試 )