jdbc還是ibatis?

公司的一個大系統的持久層一直是直接使用jdbc。在jdbc的基礎上,又自制了一個簡陋的cache。

每個持久功能的實現都比較類似,大致相當於這樣:
[code]
MyProprietaryConnection conn = ConnectionManager.checkOut(Database.DB_NAME);
try {
PreparedStatement stmt = conn.getPreparedStatement("some statement id, identifying a sql statement in an xml file");
stmt.setString(1, "param 1");
stmt.setInt(2, param2);
...
try {
ResultSet resultSet = stmt.executeQuery();
try{
while(resultSet.next()) {
...
}
}
finally {
resultSet.close();
}
}
finally {
stmt.close();
}
}
finally {
ConnectionManager.checkIn(conn);
}
[/code]
當然,各個功能的實現不完全一樣,有的有事務,有的沒有;有的忘了關閉statement,有的忘了checkIn connection;有的在出現Error的時候忘了rollback。等等等等。

dao層的代碼就是調用這些不同的jdbc代碼,然後再包上一層HashMap做cache:
[code]
Object cacheKey = ...;
synchronized(cache) {
Account acct = (Account)cache.get(cacheKey);
if(acct == null) {
acct = runJdbcForAccount(...);
cache.put(cacheKey, acct);
}
return acct.cloneAccount();
}
[/code]
當然,還要自己實現cloneAccount()。
所有對Account, Contribution, Plan之類的cache代碼也類似。

後來鑑於偶爾出現資源泄漏問題,一個程序員寫了一個jdbc模板,長成這個樣子:
[code]
abstract class PersisterCommand {
protected abstract void populateStatement(PreparedStatement stmt);
protected abstract Object processResult(ResultSet resultSet);
protected abstract boolean isTransactional();
protected abstract PreparedStatement getStatement();
public Object run() {
MyProprietaryConnection conn = ConnectionManager.checkOut(Database.DB_NAME);
try {
PreparedStatement stmt = getStatement();
populateStatement(stmt);
...
try {
if(isTransactional()) {
conn.startTransaction();
}
ResultSet resultSet = stmt.executeQuery();
try{
Object result = processResult(resultSet);
if(isTransactional()) {
conn.commitTransaction();
}
return result;
}
catch(Exception e){
if(isTransactional()) conn.rollbackTransaction();
throw e;
}
finally {
resultSet.close();
}
}
finally {
stmt.close();
}
}
finally {
ConnectionManager.checkIn(conn);
}
}
}
[/code]
然後上面的代碼可以簡化爲僅僅重載這四個抽象函數:
getStatement負責取得某個特定的sql statement;populateStatement負責填充參數;processResult負責把ResultSet轉換成domain object;isTransactional制定是否使用事務。

介紹了這麼多背景情況,希望你已經看到了,原來的直接jdbc方法是非常繁瑣,容易出錯,代碼量大,而且重複很多。
這個PersisterCommand也有很多侷限:
1。它只能處理一個connection一個statement,不能做batch。
2。它在Error出現的時候沒有rollback。
3。子類仍然要針對jdbc api寫一些有重複味道的代碼。
4。代碼不容易單元測試。因爲ConnectionManager.checkOut()和ConnectionManager.checkIn()都是寫死的。


另外,這個自制的cache也是一個重複代碼的生產者。

針對這種情況,我本來想繼續重構,弄出一個CacheManager和更靈活的jdbc模板。但是後來一想,倒還不如直接用ibatis來得好。畢竟ibatis已經是被業界廣泛使用的工具,總比自己製造輪子強。而且,相比於hibernate,ibatis也有更貼近我們現在的模型和使用習慣的優勢。


我的一個同事(公司的元老),開始是對ibatis很感興趣的。

可惜的是,當我完成了ibatis的集成,他試用了一下之後就改變了主意。這個同事在項目組甚至整個公司說話都是很有分量的,不說服他,推廣ibatis就面臨夭折的可能。

我的ibatis的集成長成這個樣子:
[code]
public interface Action {
Object run(SqlMapSession session);
}
public class IbatisPersistence {
public SqlMapSession openSession();
public Object queryForObject(String key);
public List queryForList(String key);
public int update(String key, boolean useTransaction);
public int delete(String key, boolean useTransaction);
public int update(String key);
public int delete(String key);
public Object run(Action action);
}
[/code]

這樣,除非用戶代碼調用openSession(),其它的函數都自動處理了Session的關閉。事務處理用一個boolean參數來控制也相當簡單。

上面的那麼多jdbc代碼和cache代碼最終就可以直接變成:
[code]
Accunt acct = persistence.queryForObject("getAccountById", accountId);
[/code]

那麼同事對這個東西的意見在哪裏呢?
1。他和另外一個同事爲調試一個使用了ibatis的程序bug花了一天時間。後來把ibatis刪掉,直接用jdbc就修好了。
當時我在休假,回來後一看,這個bug首先是一個stored proc的bug。他們花很多時間在ibatis裏面找問題其實都是瞎耽誤工夫;其次,在他們到處找ibatis的問題的時候,註釋掉了兩行關鍵代碼,後來忘了放回來,所以才發生stored proc修好後,ibatis代碼還是不工作,直到換了jdbc才修好。
雖然我解釋了原因,同事堅持認爲ibatis過於複雜。如果它花了他這麼長時間來debug,別人也有可能因爲種種原因花很多時間來debug別的問題。
2。ibatis只支持一個參數。這個我也解釋了,你可以用java bean或者Map。可是同事認爲這也是ibatis的學習曲線問題。如果採用,就要求大家都去學ibatis doc纔行。
3。同事原來期待的是象Active Record那樣的革命性的提高和簡化。象ibatis這樣還是要進行手工mapping的,對他來說就是沒什麼太大意義。他不覺得在java裏面做這個mapping有什麼不好。(我想,也許Hibernate對他更有吸引力。不過把這個系統轉換爲Hibernate這工作量可大多了)
4。當我說ibatis可以節省很多資源管理的重複代碼時,同事說他可以用PersisterCommand。我說PersisterCommand的這些侷限性的時候,他說,他不在乎。大不了直接寫jdbc。
5。一致性問題。如果同時用jdbc和ibatis,大家就要學兩個東西,造成混淆。而如果要把所有東西都換成ibatis,工作量是一個方面,所有的人都要學習ibatis這個代價也是很大的。
6。同事認爲cache的那點重複代碼無所謂。即使有一些降低cache hit ratio的bug也不是什麼大不了的。

最後無法達成統一意見。因爲你說什麼優點的時候,他只要一句“我不在乎”你就無話可說了。


在這些論點裏面,我也認可ibatis的學習曲線和一致性問題。可是,總不能就永遠任由這個持久層代碼這麼濫下去吧?在java這個領域裏,我幾乎完全相信不可能出現Active Record等價的東西的。而無論Hibernate還是jpa,只怕都是有不下於ibatis的學習曲線和更高的從遺留系統移植的代價吧?

越來越感覺自己不是一個合格的architect。因爲我缺乏說服人的能力。

你怎麼看這個問題呢?
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章