Friday, March 28, 2008

Generic DAO (3)

基本的 Generic DAO 只會找對應的 named query 去 invoke. 但有些情況下這功能未必合用,所以我便加了四個特殊 annotation 來提供更廣的應用。這四個都是 method-based 的annotation。用時只要在 interface 或 implementation class 的相關 method 前面 annotate 就可以
1) @Query
基本上與原本的功能相若。可以讓developer 直接寫 HQL, 或註明某特定 named query。Example:
public interface PersonDao extends GenericDao<Person, Long> {
___ @Query(query="select p from Person p where p.name = :name")
___ List<Person> findByNameWithProvidedQueryString(@Param("name") String personName);
}


2) @SqlQuery
當初設計是利用 Hibernate 的 Native Query, 讓 developer 可以做到基本的應用,但後來發覺 named native query 做的和這個差不多。
public interface PersonDao extends GenericDao<Person, Long> {
___ @SqlQuery(
_______ query = "select p.* from PERSON p where p.name = :name",
_______ entity = { @SqlEntity(alias = "p", entityClass = Person.class) }
___ )
___ List<Person> findBySqlNamedParam(@Param("name") String personName);
}

3) @QueryByCriteria
Hibernate 提供了一個動態生成 query 的機制,叫 Query By Criteria (QBC),QBC 還有一個特別應用叫 Query By Example (QBE)。這裡我希望有一個簡單的方法讓 developer 能寫到基本的 QBC 應用。(由於 Annotation 不能 Recursive, 所以比較複雜的 QBC, 比如 nested Criteria 暫時還是沒法用 annotation 表達)
public interface PersonDao extends GenericDao<Person, Long> {

___ @QueryByCriteria(
_______ criteria = @QbcCriteria (
___________ alias={@QbcAlias(aliasName = "n", field = "names")},
___________ restriction = {
_______________ @QbcRestriction(field = "n.name", operator = LIKE, param = "name", matchStyle = MatchStyle.START)
___________ }
_______ ),
_______ resultTransformer = QueryResultTransformer.DISTINCT_ROOT_ENTITY
___ )
___ public List<Person> findDistinctPersonByQbc(@Param("name")String name);
}


4) @Implemented
上面的 annotation-based query builder 如果都不能達到想要的效果,就利用這 annotation 讓 user 自己弄個 implementation class, 再在裡面自己 implement 這個 method 好了。

大致上,是 interceptor 攔截 find* 的 method call, 便 invoke GenericDao Impl 的 executeFinder() method。而 executeFinder 則會嘗試找尋究竟這次 invoke 的 method 有什麼annotation (先找 implementation class, 沒有上述四個 annotation 再找 interface), 然後根據不同的 annotation 執行不同的 logic。比如是 @Query 或 @SqlQuery, 便嘗試解拆 annotation 並執行對應的 Query/SqlQuery;@QueryByCriteria 的話,便建立並執行 Criteria;@Implemented 的話便什麼都不做,直接 invoke implementation class 的 method。

除了上面四個 finder method annotation,還有 parameter level 的 annotation 來配合:
1) @Param
用來為 method parameter 命名,用以 bind 到 query 的 named parameters, 又或者用來於 @QueryByCriteria 裡面指明某 parameter 時用到。

2) @MaxResults
用於 annotate int 的 parameter。代表 return 多少筆 result。

3) @FirstResult
用於 annotate int 的 parameter。代表 return 的第一筆 result 是整體的第幾筆。@MaxResults 和 @FirstResult 通常是兩者配合,用來作 pagination。Generic DAO 的 execute finder 處理時其實只是把值塞到 Query/SQLQuery/Criteria 的 setFirstResult() 和 setMaxResults()。

另外如果有 parameter 的 type 是 EnquiryParam 的話會有特別處理。EnquiryParam 當中可以為 enquiry 定義很多不同的東西,包括
1) Sorting fields 及其 sorting 方向
2) 同樣可以定義 first result 和 max results
3) like 比對時用什麼方法比對(QBC 時有效),是整個字段 match, 還是 match anywhere, 還是 match 開始位置。
4) 字串比對時 match case 還是 ignore case(QBC 時有效)。

至於怎樣達成這些功能,因為 code 實在太長,就不貼出來了,反正 idea 比較重要(遲些有機會才整理出來吧)。