Thursday, November 15, 2007

Generic DAO (2) 基本的 Generic DAO Implementation

上一篇提到的 Generic DAO 的效果, 旨在讓使用者不需要再重新 implement 每一個 DAO 大同小異的部份 : create, update, delete etc. 就連 finder methods, 也只要寫好 HQL 就好了, 不必再重覆每個finder 去 getSession(), createQuery() 等等.

這個 framework 的第一步, 就是 GenericDao(從原文抄來借用一下 :P ).

Generic DAO 大概的樣子就是
public interface GenericDao<T, PK extends Serializable> {
_ PK create(T newInstance);
_ T read(PK id);
_ void update(T transientObject);
_ void delete(T persistentObject);
}

在 Generic DAO 本身就有提供最基本的CRUD operation

public class GenericDaoHibernateImpl <T, PK extends Serializable>
___ implements GenericDao<T, PK>, FinderExecutor {
___ private Class<T> type;

___ public GenericDaoHibernateImpl(Class<T> type) {
_______ this.type = type;
___ }

___ public PK create(T o) {
_______ return (PK) getSession().save(o);
___ }

___ public T read(PK id) {
_______ return (T) getSession().get(type, id);
___ }

___ public void update(T o) {
_______ getSession().update(o);
___ }

___ public void delete(T o) {
_______ getSession().delete(o);
___ }

___ // Not showing implementations of getSession() and setSessionFactory()
}


(原本的 implementation 是在 Spring Config 中是在Spring config inject Hibernate 的 SessionFactory. 但我則建議用 JPA 的 EntityManager, 並利用 @PersistenceContext 讓 Spring 自動 inject EntityManager. 詳細做法之後會解釋.)

好了, 有了這個 Generic DAO implementation, 但怎樣才能沒有 implementation 也能跑? 這就要借助 Spring 的 ProxyFactoryBean. 利用 ProxyFactoryBean, Spring 會為我提供的 interfaces 自動 Generate 一個 implementation class. 比對前一篇的 Spring

<bean id="abstractDaoTarget"
_______ class="foo.GenericDaoHibernateImpl"
_______ abstract="true" />

<bean id="staffDao" class="org.springframework.aop.framework.ProxyFactoryBean">
_ <property name="proxyInterfaces" value="foo.StaffDao,foo.FinderExecutor">
_ <property name="target">
___ <bean parent="abstractDaoTarget">
_____ <constructor-arg>
_______ <value>foo.Staff</value>
_____ </constructor-arg>
___ </bean>
_ </property>
</bean>



簡單的解釋, 上面的 config 會叫 Spring 生成一個 GenericDaoHibernateImpl, 然後再生一個 Proxy Bean 來包著之前的Generic DAO Impl, 此 Proxy 亦會額外 implement StaffDao 和 FinderExecutor 的 interface. 但問題是, StaffDao 和 FinderExecutor 的 method, 這個 Proxy Bean 怎樣 implement? 其實 Proxy Factory Bean 不會做任何有意義的 implementation. 這個 Dummy 的 object instance 旨在讓 Spring AOP 的 Interceptor 攔截再跑自己想要的 logic. 那些自動 generate 出來的沒用 method implementation 是不會用到的.

原本的 JGenericDao 做法順便把 Interceptor 塞給ProxyFactoryBean. 但這做法在 Spring 2.0.x, 如果同時有用到新的 AOP config 就會不正常運作, 所以我利用了 AOP 的 xml config 來提供 interceptor

<bean id="queryInterceptor"
___ class="foo.FinderIntroductionInterceptor" />

<aop:config>
___ <aop:aspect ref="queryInterceptor">
_______ <aop:pointcut id="findQuery"
___________ expression="execution(* foo..*Dao.find*(..)) and this(foo.dao.finder.FinderExecutor)" />
_______ <aop:around pointcut-ref="findQuery" method="invokeFind" />
___ </aop:aspect>

</aop:config>



這樣每逢跑 DAO 中以 find 開首的 methods, 我們的 queryInterceptor 就會攔截.

攔截了要做什麼? 借用 "Don't repeat the DAO!" 的 code, 就是攔截後, 不直接 invoke 原本的 method, 轉而 invoke target class 的 "executeFinder" method.

public class FinderIntroductionInterceptor implements IntroductionInterceptor {

___ public Object invokeFind(MethodInvocation methodInvocation) throws Throwable {

_______ FinderExecutor genericDao = (FinderExecutor) methodInvocation.getThis();

_______ String methodName = methodInvocation.getMethod().getName();
_______ if (methodName.startsWith("find")) {
___________ Object[] arguments = methodInvocation.getArguments();
___________ return genericDao.executeFinder(methodInvocation.getMethod(), arguments);
_______ } else {
___________ return methodInvocation.proceed();
_______ }
___ }

___ public boolean implementsInterface(Class intf) {
_______ return intf.isInterface() && FinderExecutor.class.isAssignableFrom(intf);
___ }
}


Generic DAO 的 executeFinder 做的, 則是基於 method name, 去找 named query 來 invoke. 如果 method 有 parameter pass in 的話, 就順便 bind 到 query.
public List<T> executeFinder(Method method, final Object[] queryArgs) {
____ final String queryName = queryNameFromMethod(method);
____ final Query namedQuery = getSession().getNamedQuery(queryName);
____ String[] namedParameters = namedQuery.getNamedParameters();
____ for(int i = 0; i < queryArgs.length; i++) {
____________ Object arg = queryArgs[i];
____________ Type argType = namedQuery.setParameter(i, arg);
_____ }
_____ return (List<T>) namedQuery.list();
}

public String queryNameFromMethod(Method finderMethod) {
____ return type.getSimpleName() + "." + finderMethod.getName();
}



這個便是 Generic DAO 最基本的功能.

但這個 Generic DAO 有很多欠缺的功能, 下回就是我自己基於這個 framework 作的新功能. 當中包括 annotated parameters, provided Query, annonatation-based QBC and QBE, 和 self-implemented finders.

Wednesday, November 14, 2007

Generic DAO (1)

反正丟空了這麼久, 就用這個 blog 來做學習記錄吧.

在公司最近搞的 application framework 利用了 Hibernate, 在Don't Repeat DAO 這文章中找到一個 Generic DAO 的架構, 加上了自己的 extensions, 在這裡 share 想法.

顧名思義, 這個 Pattern 是希望不要再重覆又重覆的去建一大堆大同小異的 DAO. 其實每個 DAO, 主要都不外乎是 create, delete, update 和一堆 finder method. 這個 Generic DAO 的做法. 大概就是以 Hibernate 去處理掉 CRUD 中的 Create, Update, Delete 和最基本的 Read, 然後以最簡單的方法去讓 developer 去 define 其他 finder methods. 而define finder method, 也讓 developer 只要集中於 query 上, 而不用再重覆寫一些 getSession(), session.createQuery(), return query.list() 之類的東西.

當中用到的主要 technology 有 Spring (AOP 的 Interceptor 和 ProxyFactoryBean) 和 Hibernate (當然)。

先說說基本效果.
假設我寫了一個 Staff class, 然後要做 DAO. 靠這個 framework, 我要做的是:

1. 在 Staff class 中加上 OR Mapping. 我選擇了用 annotation.

@Entity
@Table(name="STAFF")
public class Staff {
_ @Id
_ @GeneratedValue(strategy=GenerationType.AUTO)
_ @Column(name="ID")
_ private Long id;

_ @Column(name="NAME")
_ private String name;

_ @Column(name="SALARY")
_ private BigDecimal salary;

_ // getters and setters and other methods
}


2. 寫 DAO 的 Interface

public interface StaffDao extends GenericDao<Staff, Long> {
}



3. 在 Spring Config 加上

<bean id="abstractDaoTarget" class="foo.GenericDaoHibernateJpaImpl" abstract="true" />

<!-- Dao Layer instances -->
<bean id="staffDao" class="org.springframework.aop.framework.ProxyFactoryBean">
_ <property name="proxyInterfaces" value="foo.StaffDao">
_ <property name="target">
___ <bean parent="abstractDaoTarget">
_____ <constructor-arg>
_______ <value>foo.Staff</value>
_____ </constructor-arg>
___ </bean>
_ </property>
</bean>
和在 persistence.xml 加上 Staff class 的名字

4. DAO 就可以用了. 對, 沒有任何 implementation class, 就已經有一個 提供基本四個 CRUD operation 的 DAO 可以用了. 或者你會說, 這個 DAO 沒有什麼用, 最重要的 finder method 都沒有, 我要用 Staff name search Staff 怎麼做? 好的, 要加 finder methods, 只要:

5. 修改一下 StaffDao, 加進你想要的 finder method
public interface StaffDao extends GenericDao<Staff, Long> {
_ public void List<Staff> findStaffByName(String name);
}

6. 在 Staff class 前面加上

@NamedQueries({
_ @NamedQuery(name="Staff.findStaffByName",
______ query="from Staff s where s.name= :name")
})

一樣毋需任何 implementation, 你的 DAO 就已經提供了 finder.

怎樣達到這個效果, 下一篇會開始解釋. 文章開首的 link 提供了大概的做法.

(由於 Blogger.com 對於 source code 的顯示非常不方便, 一直吃掉我的 space, 所以大家就自己把 source code 前的 underscore 消去吧.)