如果某 POM 指明了 deployment 的 target repository, 我想 deploy 往另一個 repository (比如 download 了 third party 的 source, deploy 往自己的 repository), 可以:
mvn deploy -DaltDeploymentRepository=serverId::default::ftp://host/path
serverId 是在 settings.xml 的 server
ftp 那位置是 protocol, 比如 ftp, scp, file 等.
Tuesday, June 24, 2008
Friday, May 30, 2008
找出 jar 之間的 dependency
有個好工具能找出一堆 Jar 之間的相互 dependency, 當你要為一堆 third-party JAR 寫 POM deploy 上 maven 的話, 這尤其有用. 這工具叫 jarjar. 找 dependency 只是 jarjar 其中一個小功能.
最基本用法 (on windows, 在 Unix 請改動最後那段 classpath):
> java -jar jarjar.jar find jar a.jar;b.jar;c.jar
C:\tmp\a.jar -> C:\tmp\b.jar
C:\tmp\b.jar -> C:\tmp\c.jar
這樣你就很容易明白, a depends on b, 然後 b depends on c.
可以用 mustang-style 的 classpath syntax:
> java -jar jarjar.jar find jar "./*"
最基本用法 (on windows, 在 Unix 請改動最後那段 classpath):
> java -jar jarjar.jar find jar a.jar;b.jar;c.jar
C:\tmp\a.jar -> C:\tmp\b.jar
C:\tmp\b.jar -> C:\tmp\c.jar
這樣你就很容易明白, a depends on b, 然後 b depends on c.
可以用 mustang-style 的 classpath syntax:
> java -jar jarjar.jar find jar "./*"
Wednesday, May 14, 2008
Maven 筆記 (1) - Maven 與 Ant 的分別
這裡說的 Maven 是 Maven2
1) Ant 可以說是一個 script,當中要把每個 step 寫出。Maven 是 Declarative 的,不是告訴 build tools "怎樣做",而是告訴 build tools "做什麼"。
例如在 Ant 中你要寫:建立 output directory,然後 compile 某 source directory 裡的 source 往 output directory, 再 copy 某些 resource files 到 output directory, 然後再 jar 成什麼什麼名字。
在 Maven 中則只是宣告:source directory 在哪裡,output directory 在哪,我的 project 要 build 成 jar (還是 war/ear etc),我的成品要叫什麼名字。
2) Ant 對於每樣做的事都需要逐一 Config 好。Maven 則是主張 Convention over Configuration。有些東西定好了 Convention (慣例) 後,只要你跟從該慣例,便不需額外做 configuration。
比如,如果我的 source directory 跟從了 maven 所定的慣例,我的 POM.xml (Maven 的 "build script") 只需要定義這個 project 的名稱等,連 compile 的 source directory 都不需設定,就已經能交給 maven 去了:http://maven.apache.org/guides/introduction/introduction-to-the-pom.html#Minimal_POM
Maven 與 Ant 整個設計哲學簡直是南轅北轍,孰佳孰劣很難比較。但綜觀而言,對於一般的 project, 使用 Maven 能逼使你把 project 結構想好 (一個混亂的結構與依賴關係的 project 套用 Maven 會很困難)。Maven 的 pom.xml 比起 Ant 的 build.xml 看起來更一目瞭然。但一些特別的 step, 在 Ant 中可能寫幾句就可以達到,在 Maven 中,要是沒有適當的 plugin, 做起來可能很麻煩。
1) Ant 可以說是一個 script,當中要把每個 step 寫出。Maven 是 Declarative 的,不是告訴 build tools "怎樣做",而是告訴 build tools "做什麼"。
例如在 Ant 中你要寫:建立 output directory,然後 compile 某 source directory 裡的 source 往 output directory, 再 copy 某些 resource files 到 output directory, 然後再 jar 成什麼什麼名字。
在 Maven 中則只是宣告:source directory 在哪裡,output directory 在哪,我的 project 要 build 成 jar (還是 war/ear etc),我的成品要叫什麼名字。
2) Ant 對於每樣做的事都需要逐一 Config 好。Maven 則是主張 Convention over Configuration。有些東西定好了 Convention (慣例) 後,只要你跟從該慣例,便不需額外做 configuration。
比如,如果我的 source directory 跟從了 maven 所定的慣例,我的 POM.xml (Maven 的 "build script") 只需要定義這個 project 的名稱等,連 compile 的 source directory 都不需設定,就已經能交給 maven 去了:http://maven.apache.org/guides/introduction/introduction-to-the-pom.html#Minimal_POM
Maven 與 Ant 整個設計哲學簡直是南轅北轍,孰佳孰劣很難比較。但綜觀而言,對於一般的 project, 使用 Maven 能逼使你把 project 結構想好 (一個混亂的結構與依賴關係的 project 套用 Maven 會很困難)。Maven 的 pom.xml 比起 Ant 的 build.xml 看起來更一目瞭然。但一些特別的 step, 在 Ant 中可能寫幾句就可以達到,在 Maven 中,要是沒有適當的 plugin, 做起來可能很麻煩。
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:
2) @SqlQuery
當初設計是利用 Hibernate 的 Native Query, 讓 developer 可以做到基本的應用,但後來發覺 named native query 做的和這個差不多。
3) @QueryByCriteria
Hibernate 提供了一個動態生成 query 的機制,叫 Query By Criteria (QBC),QBC 還有一個特別應用叫 Query By Example (QBE)。這裡我希望有一個簡單的方法讓 developer 能寫到基本的 QBC 應用。(由於 Annotation 不能 Recursive, 所以比較複雜的 QBC, 比如 nested Criteria 暫時還是沒法用 annotation 表達)
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 比較重要(遲些有機會才整理出來吧)。
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 比較重要(遲些有機會才整理出來吧)。
Thursday, February 14, 2008
Spring Batch 記錄
1) SimpleJobLauncher default 用 SyncExecutor. 所以 jobLauncher.run(job, jobParam) 會等 job 跑完才 return. 可以在 app context config inject Async Task Executor (or Thread Pool Executor etc). 這樣 SimpleJobLauncher#run() 便會立刻 return JobExecution.
2) 暫無 completion event 之類. 要自己做 aop wrap around Job.execute() or Step.execute()
2) 暫無 completion event 之類. 要自己做 aop wrap around Job.execute() or Step.execute()
Friday, December 14, 2007
SevenMock 介紹
在做 Unit test 的人, 大概都知道要利用 Mock Object 來避開 dependencies, 但 Mock 這東西, 自己寫又很麻煩, test case 裡面每一個 test, Mock 要的反應也不太一樣, 每個 test 寫一個 Mock 的話, 既辛苦又難看. 所以才有各大 Mock Framework, 幫忙你能輕易的建立因應每一個 test 的 mock object.
出名的有 jMock, EasyMock. 但用過後, jMock 固然看得頭昏也看不明白搞什麼, EasyMock 用起來是簡單一點, 但對於 expected data 的檢查也是十分麻煩, 尤其是我只想檢查 expected input 的某些 attributes, EasyMock 做起來十分痛苦.
後來找到一個叫 SevenMock 的 mock framework, 用起來十分簡單, expected input 的 validation 的可讀性更是非常好.
下面是一個使用例子. 假設我有一個 TradeService的 實作要測:
下面便是利用 SevenMock 的測試
用起來十分直觀容易明白. 其中一個缺點是要 mock 的 interface, 需要一個 implementation class 來 override 為 anonymous class. 我這裡是弄一個 empty 的 implementation (TradeDaoMockImpl 及 FundServiceMockImpl), 靠 IDE 要弄這麼一個 empty implementation 很容易. 但要是你本身就有 implementations, 只要constructor 沒有什麼特別的 initialization, 直接用你自己的 implementation 來用也無不可, 反正不會真的 invoke 到它裡面的 method.
誠意推介.
出名的有 jMock, EasyMock. 但用過後, jMock 固然看得頭昏也看不明白搞什麼, EasyMock 用起來是簡單一點, 但對於 expected data 的檢查也是十分麻煩, 尤其是我只想檢查 expected input 的某些 attributes, EasyMock 做起來十分痛苦.
後來找到一個叫 SevenMock 的 mock framework, 用起來十分簡單, expected input 的 validation 的可讀性更是非常好.
下面是一個使用例子. 假設我有一個 TradeService的 實作要測:
public class TradeServiceImpl {
private tradeDao tradeDao;
private fundService fundService;
public void setTradeDao(TradeDao tradeDao) {
this.tradeDao = tradeDao;
}
public void setFundService(FundService fundService) {
this.fundService = fundService;
}
public boolean inputTrade(Trade trade) {
if (this.fundService.isEnoughBalance(trade.getPrice().mulplity(trade.getQty()))) {
this.fundService.deductBalance(trade.getPrice().mulplity(trade.getQty()));
trade.setStatus(Trade.Status.NEW);
this.tradeDao.createTrade(trade);
return true;
)
return false;
}
}
下面便是利用 SevenMock 的測試
public class TradeServiceImplTest {
@Test
public void testFoo() {
// create test target and mocks
TradeService tradeService = new TradeServiceImpl();
MockController mockControl = new MockController();
TradeDao tradeDao = (TradeDao)mockControl.getMock(TradeDao.class);
FundService fundService= (FundService)mockControl.getMock(FundService.class);
// Inject Mock objects to class-to-be-tested
tradeService.setTradeDao(tradeDao);
tradeService.setFundService(fundService);
// Set Expected Results
mockControl.expect(new FundServiceMockImpl() {
public boolean isEnoughBalance(BigDecimal amount){
// check if incoming value is correct
assertEquals(BigDecimal.valueOf(1000*400), amount);
// preset behavior: return true
return true;
}
});
mockControl.expect(new FundServiceMockImpl() {
public void deductBalance(BigDecimal amount){
// check if incoming value is correct
assertEquals(BigDecimal.valueOf(1000*400), amount);
}
});
mockControl.expect(new TradeDaoMockImpl() {
public void createTrade(Trade trade) {
assertEquals(Trade.Status.NEW,trade.getStatus());
return result;
}
});
// Finish setting expected behavior, then run test now
Trade myTrade = new Trade();
myTrade.setPrice(BigDecimal.valueOf(1000));
myTrade.setQty(BigDecimal.valueOf(400));
myTrade.setStatus(Trade.Status.NONE);
boolean result = tradeService.inputTrade(myTrade);
// verify results
assertTrue(result);
assertEquals(Trade.Status.NEW, myTrade.getStatus());
// Mock control will verify if there is missing step in invocation
mockControl.verify();
}
}
// dummy implementation class of mocked interfaces for use of SevenMock
class TradeDaoMockImpl implements TradeDao {
// all empty methods....
}
class FundServiceMockImpl implements FundService {
// all empty methods....
}
用起來十分直觀容易明白. 其中一個缺點是要 mock 的 interface, 需要一個 implementation class 來 override 為 anonymous class. 我這裡是弄一個 empty 的 implementation (TradeDaoMockImpl 及 FundServiceMockImpl), 靠 IDE 要弄這麼一個 empty implementation 很容易. 但要是你本身就有 implementations, 只要constructor 沒有什麼特別的 initialization, 直接用你自己的 implementation 來用也無不可, 反正不會真的 invoke 到它裡面的 method.
誠意推介.
Tuesday, December 04, 2007
Spring 中取得 advised-this 的做法
原來 Advised 拿 target 這一招不是經常成功的 orz... 待我有時間再去改改做法 :(
假設我有一個 object (假設叫是 class Foo), 外面墊了幾層 aspect, 如果我在 Foo 裡面 invoke 任何自己的 method, 那麼該 invocation 是沒有被攔截的.
那麼怎樣才能令自己 invoke 自己的 method 也能被 aspect 攔截? 那麼可以嘗試 inject advised 的 instance 給 object 自己. 在 Spring + JDK5 的環境下, 寫了一個簡單的bean post processor + annotation 去達到這目的.
BeanPostProcessor
BeanSelf 的 annotation 從略, 反正是一個 Retention at Runtime, annotate at Method 的 annotation.
自己的 class 要 invoke 自己method 時被 aspect 攔截, 便這樣做:
當然不要忘了在 Spring 的 app context config 加上 InjectBeanSelfProcessor.
Note: 謹記原來可以利用 Advised 來取得真正 target.
Reference:
http://fyting.javaeye.com/blog/109236
假設我有一個 object (假設叫是 class Foo), 外面墊了幾層 aspect, 如果我在 Foo 裡面 invoke 任何自己的 method, 那麼該 invocation 是沒有被攔截的.
那麼怎樣才能令自己 invoke 自己的 method 也能被 aspect 攔截? 那麼可以嘗試 inject advised 的 instance 給 object 自己. 在 Spring + JDK5 的環境下, 寫了一個簡單的bean post processor + annotation 去達到這目的.
BeanPostProcessor
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import org.springframework.aop.framework.Advised;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InjectBeanSelfProcessor implements BeanPostProcessor {
___ public Object postProcessAfterInitialization(Object bean, String beanName)
___________ throws BeansException {
_______ // 找出 Proxy 背後真正的 target
_______ Object target = bean;
_______ while (target instanceof Advised) {
___________ try {
_______________ target = ((Advised)bean).getTargetSource().getTarget();
___________ } catch (Exception e) {
_______________ target = null;
_______________ break;
___________ }
_______ }
_______ // 如果順利找到 target
_______ if (target != null) {
___________ Method[] methods = target.getClass().getMethods();
___________ for (Method m : methods) {
_______________ if (m.getAnnotation(BeanSelf.class) != null
_______________________ && m.getParameterTypes().length == 1
_______________________ && m.getParameterTypes()[0].isAssignableFrom(bean.getClass())) {
___________________ try {
_______________________ m.invoke(target, bean);
___________________ } catch (IllegalArgumentException e) {
___________________ } catch (IllegalAccessException e) {
___________________ } catch (InvocationTargetException e) {
___________________ }
_______________ }
___________ }
_______ }
_______ return bean;
___ }
___ public Object postProcessBeforeInitialization(Object bean, String beanName)
___________ throws BeansException {
_______ return bean;
___ }
}
BeanSelf 的 annotation 從略, 反正是一個 Retention at Runtime, annotate at Method 的 annotation.
自己的 class 要 invoke 自己method 時被 aspect 攔截, 便這樣做:
public class FooImpl implements Foo {
___ private Foo self;
___ // 加上 BeanSelf, 讓 Spring inject Advised-self 進來
___ @BeanSelf
___ public void setSelf(Foo self) {
_______ this.self = self;
___ }
___ protected Foo getSelf() {
_______ return this.self;
___ }
___ public void foo() {
_______ // 不要直接 invoke bar() 或 this.bar()
_______ getSelf().bar();
___ }
___ public void bar() {
_______ //....
___ }
}
當然不要忘了在 Spring 的 app context config 加上 InjectBeanSelfProcessor.
Note: 謹記原來可以利用 Advised 來取得真正 target.
Reference:
http://fyting.javaeye.com/blog/109236
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 大概的樣子就是
在 Generic DAO 本身就有提供最基本的CRUD operation
(原本的 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
簡單的解釋, 上面的 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
這樣每逢跑 DAO 中以 find 開首的 methods, 我們的 queryInterceptor 就會攔截.
攔截了要做什麼? 借用 "Don't repeat the DAO!" 的 code, 就是攔截後, 不直接 invoke 原本的 method, 轉而 invoke target class 的 "executeFinder" method.
Generic DAO 的 executeFinder 做的, 則是基於 method name, 去找 named query 來 invoke. 如果 method 有 parameter pass in 的話, 就順便 bind 到 query.
這個便是 Generic DAO 最基本的功能.
但這個 Generic DAO 有很多欠缺的功能, 下回就是我自己基於這個 framework 作的新功能. 當中包括 annotated parameters, provided Query, annonatation-based QBC and QBE, 和 self-implemented finders.
這個 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.
2. 寫 DAO 的 Interface
3. 在 Spring Config 加上
4. DAO 就可以用了. 對, 沒有任何 implementation class, 就已經有一個 提供基本四個 CRUD operation 的 DAO 可以用了. 或者你會說, 這個 DAO 沒有什麼用, 最重要的 finder method 都沒有, 我要用 Staff name search Staff 怎麼做? 好的, 要加 finder methods, 只要:
5. 修改一下 StaffDao, 加進你想要的 finder method
6. 在 Staff class 前面加上
一樣毋需任何 implementation, 你的 DAO 就已經提供了 finder.
怎樣達到這個效果, 下一篇會開始解釋. 文章開首的 link 提供了大概的做法.
(由於 Blogger.com 對於 source code 的顯示非常不方便, 一直吃掉我的 space, 所以大家就自己把 source code 前的 underscore 消去吧.)
在公司最近搞的 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 消去吧.)
Subscribe to:
Posts (Atom)