1. DaoZero是什么?它可以在哪方面幫助我?
*假設(shè)你具有使用Spring的iBatis支持類作為持久層實(shí)現(xiàn)的實(shí)際編碼經(jīng)驗(yàn)(即時(shí)沒有,學(xué)習(xí)Spring和iBatis也應(yīng)該不是件怎么難的事情)。
DaoZero是1個(gè)很小的Spring Java Bean。可以到http://dao-zero.sourceforge.net下載。使用DaoZero可以減少基于 iBatis+Spring的持久層代碼數(shù)量,因?yàn)镈aoZero會動態(tài)地替我們實(shí)現(xiàn)持久層接口。它不是1個(gè)Spring中iBatis支持類的包裝,而是用來直接替換掉我們手工編寫的持久層實(shí)現(xiàn)代碼的。使用DaoZero時(shí),一旦我們完成了DAO接口的定義(Java Interface),通常情況下,我們只需要再在Spring Context定義文件中聲明類型(class)為daozero.ibatis.Dao的bean,并且設(shè)置這些bean的targetType屬性為已定義好的DAO接口,然后這些DaoZero bean 就會在運(yùn)行時(shí)為我們動態(tài)地生成實(shí)現(xiàn)了targetType的DAO實(shí)現(xiàn)類,由這些實(shí)現(xiàn)類去調(diào)用iBatis API訪問數(shù)據(jù)庫。所以,不需要DAO接口的實(shí)現(xiàn)代碼了。
2. DaoZero的工作原理DaoZero約定iBatis SQL Mapping XML文件中定義的statement的名字需要和DAO接口(抽象類)的method的名字保持一致,而且,當(dāng)前版本還要求statement的parameter的數(shù)量及首次出現(xiàn)順序也必須要和DAO接口(抽象類)的method的參數(shù)(形參)的數(shù)量及出現(xiàn)順序保持一致,通過做出這些約定(應(yīng)該不太難于遵守吧),DaoZero就可以確定如何把method被調(diào)用時(shí)傳入的參數(shù)(實(shí)參)和statement的 parameter對應(yīng)起來,于是就可以用這些傳入的參數(shù)組成statement需要的parameter map,去調(diào)用iBatis API訪問數(shù)據(jù)庫。DaoZero是一個(gè)實(shí)現(xiàn)了org.springframework.beans.factory.FactoryBean 的bean,就是說它是可以產(chǎn)生bean的factory bean,而DaoZero factory bean產(chǎn)生的bean就是實(shí)現(xiàn)了DAO接口的DAO對象,這些DAO對象負(fù)責(zé)調(diào)用Spring的iBatis template的方法,例如queryForObject()、queryForList()和update()等。這些DAO對象也有足夠“智能”,它們會依據(jù)method的返回類型推斷出該調(diào)用queryForObject()還是queryForList()(當(dāng)前版本尚不支持queryForMap)。 DaoZero factory bean是如何產(chǎn)生DAO對象的呢?這要視其屬性targetType是接口還是抽象類來定:如果targetType是接口,那么使用JDK標(biāo)準(zhǔn)的 proxy(java.lang.reflect.Proxy)機(jī)制,由該proxy負(fù)責(zé)攔截下對該接口方法的調(diào)用;如果是抽象類,那么就使用CGLIB的enhancer(net.sf.cglib.proxy.Enhancer)萊攔截下對該抽象類中抽象方法的調(diào)用,而將方法調(diào)用攔截下來后的處理則基本上一致。使用JDK Proxy或CGLIB enhancer對性能的影響在數(shù)百納秒(ns)這個(gè)數(shù)量級,因此對于大多數(shù)Web應(yīng)用來說相對于數(shù)據(jù)庫SQL執(zhí)行是可以忽略不計(jì)的。
事實(shí)上DaoZero不得不hack了一些iBatis的代碼,不得不把iBatis提供的一些接口強(qiáng)行轉(zhuǎn)型(Cast)到iBatis的內(nèi)部實(shí)現(xiàn)類,原因在于iBatis似乎沒有提供檢索其statement元數(shù)據(jù)的方法,使得DaoZero不得不在代碼中留下了一些壞味道。(所以,如果iBatis出現(xiàn)了大的版本改變,那么DaoZero這部分代碼也不得不重新寫。)
3. 用DaoZero代替原來的iBatis DAO bean假設(shè)我們有一個(gè)數(shù)據(jù)庫表叫"account",表結(jié)構(gòu)如下所示,
create table account (
userid varchar(80) not null,
email varchar(80) not null,
constraint pk_account primary key (userid)
);
我們使用了一個(gè)叫Account的domain class來代表該表,Account是標(biāo)準(zhǔn)的Java Bean(POJO),具有屬性:"userId"和"email",
public class Account implements Serializable {
private String userid;
private String email;
public String getUserId() { return this.userid; }
public void setUserId(String s) { this.userid=s; }
public String getEmail() { return this.email; }
public void setEmail(String s) { this.email=s; }
}
操作Account的DAO接口是這樣的,
public interface AccountDao {
Account getAccountByUserId(String userId) throws DataAccessException;
void updateAccount(Account account) throws DataAccessException;
List getUsernameList() throws DataAccessException;
}
使用DaoZero之前,一般情況下,我們會用iBatis做一個(gè)DAO實(shí)現(xiàn)類,該類繼承自Spring的SqlMapClientDaoSupport,該基類封裝了iBatis SqlMapClient的主要操作以和Spring集成起來,該實(shí)現(xiàn)類可能是這樣的:
import org.springframework.org.ibatis.support.SqlMapClientDaoSupport;
public class AccountDaoImpl extends SqlMapClientDaoSupport implements AccountDao {
?? public Account getAccountByUserIdAndEmail(String userId, String email) throws DataAccessException {
????Map params = new HashMap();
????params.put( "userId", userId );
????params.put( "email", email );
????return getTemplate().queryForObject( "getAccountByUserIdAndEmail", params );
?? }
??public int updateAccount(Account account) throws DataAccessException {
????return getTemplate().update( "updateAccount", account );
?? }
?? public List getUsernameList() throws DataAccessException;
????return getTemplate().queryForList( "getUsernameList", null );
?? }
自然,iBatis的SQL Mapping XML文件是必不可少的:
<select id="getAccountByUserIdAndEmail" resultClass="Account">
????select * from account where userid=#userId# AND email=#email#
</select>
<select id="getUsernameList" resultClass="java.lang.String">
????select userid from account
</select>
<update id="updateAccount">
????update account set email = #email# where userid=#userId#
</update>
然后,我們通常會到Spring Context XML文件中為該DAO聲明1個(gè)Spring Bean?:
<bean id="accountDao" class="AccountDaoImpl">
???? <property name="sqlMapClient" ref="sqlMapClient"/>
</bean>
現(xiàn)在,我們來試試看使用了DaoZero后DaoZero怎樣來去掉AccountDaoImpl那些繁復(fù)無聊的代碼:很簡單,第一步是刪掉那個(gè)AccountDaoImpl.java! 然后,修改上面那段Spring Context XML文件中bean的定義,改成:
<bean id="accountDao" class="daozero.ibatis.Dao">
???? <property name="sqlMapClient" ref="sqlMapClient"/>
????<property name="targetType" value="AccountDao" /> <!-- interface -->
</bean>
可以看到,新的使用DaoZero的方式用的bean的class是DaoZero庫提供的1個(gè)固定的類--"daozero.ibatis.Dao"該類會在運(yùn)行時(shí)自動提供AccountDao接口的實(shí)現(xiàn)類,所以AccountDaoImpl.java就可以扔掉了,就這么簡單。
4. 使用抽象類的例子雖然上述例子足以應(yīng)付多數(shù)情況,但是DaoZero不可能為我們完成任何事情,有時(shí)候還是有不得不手工實(shí)現(xiàn)一些DAO方法的需要的,這時(shí)候我們?nèi)绾渭缺A粝翫aoZero提供的好處, 又能夠告訴DaoZero哪些方法我們已經(jīng)自己手工實(shí)現(xiàn)了而不需要DaoZero代勞了呢?解決辦法是很自然的,寫好我們自己的DAO類,但是只需要填上需要手工實(shí)現(xiàn)的,其他的可以繼續(xù)由DaoZero代勞, DaoZero會依據(jù)該實(shí)現(xiàn)類的某方法是否是abstract來判斷是否需要替我們?nèi)プ鍪虑椋恍枰脑捑褪裁匆膊蛔觯苯愚D(zhuǎn)交給我們填好的方法代碼,所以,這時(shí)候的DAO實(shí)現(xiàn)類是抽象類。另外需要做的事情就是把bean的targetType換成這個(gè)abstract的DAO實(shí)現(xiàn)類。對于上面那個(gè)例子,我們保留下AccountDaoImpl.java,手工填寫updateAccount()方法:
public abstract class AccountDaoImpl implements AccountDao {
public abstract int __updateAccount(Account account) throws DataAccessException;
public int updateAccount(Account account) throws DataAccessException {
????// Insert additional operation code here
????return __updateAccount(account);
}
}
最后,需要修改SQL mapped statement的名字,改成"__updateAccount",以便讓DaoZero了解statement __updateAccount是和DAO方法__updateAccount聯(lián)系在一起的:
<select id="getAccountByUserIdAndEmail" resultClass="Account">
????select * from account where userid=#userId# AND email=#email#
</select>
<select id="getUsernameList" resultClass="java.lang.String">
????select userid from account
</select>
<update id="__updateAccount">
????update account set email = #email# where userid=#userId#
</update>
當(dāng)然,DaoZero并不反對你不修改statement名字,不增加一個(gè)__updateAccount方法,而是在updateAccount()方法中按照從前的寫法自己調(diào)用iBatis API(不過,既然改改statement的名字 就可以讓DaoZero繼續(xù)為我們服務(wù),而且能保持代碼的一致性,我想通常還是加上個(gè)__updateAccount方法更合適一些,這也是DaoZero推薦的一個(gè)實(shí)踐 -- DaoZero希望你關(guān)注于完成DaoZero替你 代勞不了的事情,而不是去寫那些重復(fù)性很大的代碼。)
5. 使用當(dāng)前版本的DaoZero的一些限制條件# 同一個(gè)類中不允許有同名方法(名字重載),除非這些同名方法的參數(shù)個(gè)數(shù)不一樣(否則,DaoZero為statement尋找對應(yīng)方法時(shí)會因出現(xiàn)歧義而失敗)。
# 由DaoZero負(fù)責(zé)實(shí)現(xiàn)的抽象方法必須是public abstract。因?yàn)楫?dāng)前版本的DaoZero使用Java標(biāo)準(zhǔn)的Reflection機(jī)制(Class.getMethods())來遍歷DAO類的method,所以非public abstract的方法是找不到的,這是一個(gè)可以改進(jìn)的限制條件 -- DaoZero計(jì)劃在后續(xù)版本中自行分析class文件來找到protected abstract方法,這樣就可以不用強(qiáng)迫我們不得不把內(nèi)部方法也public出來了;
# 目前DaoZero尚不支持iBatis API中的queryForMap()方法和Pagination機(jī)制(queryForMap在實(shí)現(xiàn)計(jì)劃之中,而pagination我正考慮如何實(shí)現(xiàn),而且既然使用iBatis就是為了獲得使用native SQL 的靈活性和性能,我個(gè)人更傾向于不使用iBatis的pagination作為分頁支持機(jī)制,而習(xí)慣于利用數(shù)據(jù)庫SQL對分頁的支持語法);
# 雖然目前只支持iBatis,但為Hibernate給出一個(gè)DaoZero實(shí)現(xiàn)也是在計(jì)劃之中(因?yàn)槲液筒簧偃艘粯樱蚕矚gHibernate);
6. 使用時(shí)出現(xiàn)問題?# 確保參數(shù)的數(shù)量和首次出現(xiàn)順序在statement和method中是保持一致的,而且必須是abstract,根據(jù)使用經(jīng)驗(yàn),大多數(shù)錯(cuò)誤來自于此;
# 看看是否滿足第5節(jié)說明的一些限制條件,很可能忽略的一種情況就是把method做成protected abstract了。
7. 其他實(shí)際上還有1個(gè)小技巧:namespace -- 是的,這可以用于支持iBatis的namespace機(jī)制,但是1個(gè)Dao只能限定于1個(gè)namespace。具體使用方法請參照下載下來的sample。Sample是修改自Spring的JPetstore例子,比較一下它和原始的Spring的JPetstore的實(shí)現(xiàn)例子,可以找到DaoZero提倡的如何在service層使用DAO的理念,例如DaoZero更提倡不要只把service作為dao層的delegation,提倡大部分對DAO的操作的組合都放到service層中去。
8. License:Apache License Version 2.0