星期日, 5月 15, 2005

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 裡。

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;
}
}
接下來就是 AutoProxyCreator,當它遇到 bean 的 class 為 HibernateDAOWrapper 的 subclass 時,它就會自動替該 bean 做 proxy。

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 測試 )

2 Comments:

At 12:22 上午, Anonymous 匿名 said...

想請教一下.最近和朋友想要寫一些程式.前遄的部份由他處理.後端包含(Business及Domain Data)的部份由我來負責.我朋友的想法.是想要動態的去做Domain Object的存取.也就是希望用Hibernate中的Dynamic Model.可是我本身不是很喜歡用這個東西.我在Hibernate In Action中有提到.使用Dynamic Model的缺點,這樣變成不再是一個確保data integrity的data model了.而且在未來有可能產生unpredicatable behavior, lost data, and expensive maintenance.當應用程式不再具有有效性時,資料結構的語意也會消失.
我對這個概念沒有很深的體會,可以請問你的看法嗎?這篇內容在原書的pg 391和pg 392. Title是"C.5 Dynamically unsaft"..感謝.

 
At 2:27 上午, Blogger ingramchen said...

Dynamic Model 我只用過一次,那次的需求是要複製許多的 table 到其他資料庫去。因為不想替所有要 copy 的 table 做 mapping,所以才會想用 Dynamic Model 來做... 不過 Hibernate 的作者群對這個的興趣不是很大,在 forum 裡,我也曾看到他們很懷疑這個功能到底有沒有人在用,甚至想把它 deprecate....

個人的意見是能夠不用就不用,因為那真的不是 database 了... 如果要用也請把範圍縮小,比方說我上面提到的,我只拿來 copy table 而已。又或者有些需求是需要動態改變 table 的欄位的,這時 Dynamic Model 才算是派的上用場。當然這種改變 table 欄位的情形理論上不多,對整體而言影響應該不大。

一點淺見... (不知有沒有回答到...?)

 

張貼留言

<< Home