這篇文章源自剛開發(fā)的一個小項目。項目中并未使用hibernate,但我還是要把它放在hibernate欄下。理由很簡單,技術(shù)是死的,而人是活的,能熟練的使用一項技術(shù)不算什么,但能恰當(dāng)?shù)倪x擇相應(yīng)的技術(shù),甚至自己想出辦法來優(yōu)雅的解決實際問題,就需要一定的積累了。而這種積累就來源自項目實踐和對各種技術(shù)其實質(zhì)的理解。我記得在某個論壇上某人(名字忘了)說過一句話:如果學(xué)習(xí)hibernate只是學(xué)會了怎么mapping,怎么寫DAO,就只是學(xué)了皮毛,學(xué)hibernate就要從中了解到很多持久層的最佳實踐。我深以為然!
項目很簡單,頁面也不多,但頁面字段較多(100多個),相互間還有一定關(guān)聯(lián)。而且存入數(shù)據(jù)庫類型多樣,有varchar, integer和date幾種。我是很希望用hibernate來實現(xiàn)的,但考慮到項目時間較緊,而我對hibernate的了解還是停留在理論和學(xué)習(xí)階段(有點(diǎn)落后啊!),采用不熟悉的技術(shù)項目風(fēng)險較大,所以還是使用普通JDBC作為持久層方案。面對這么多字段,一個個去拼SQL語句,代碼臃腫,而且容易出錯,也難以維護(hù)。我得對這幾種類型的字段,在插入、更新和讀取時分別處理,寫一個private方法,傳入類型和字段名,讀取相應(yīng)的ResultSet,是不錯的方法,至少是比較優(yōu)雅的實現(xiàn)。
什么是優(yōu)雅?動態(tài)設(shè)置,避免hardcode,就是優(yōu)雅;層次清晰,層次間耦合最低,就是優(yōu)雅;只寫一處,處處引用,就是優(yōu)雅;代碼精煉,避免過度設(shè)計,就是優(yōu)雅;接口明確,調(diào)用簡單,就是優(yōu)雅;調(diào)試容易,便于測試,就是優(yōu)雅。。。。。。而優(yōu)雅的設(shè)計和實現(xiàn),在可擴(kuò)展性、可維護(hù)性、開發(fā)效率、開發(fā)成本等方面都是最好的。
Hibernate就是優(yōu)雅的設(shè)計,它通過配置文件,建立實體與數(shù)據(jù)庫的映射,動態(tài)的生成SQL語句,避免了對屬性字段的hardcode,這就是它最本質(zhì)的思想。我不使用hibernate,但一樣可以借鑒它的思想,我不需要對象容器、分頁查詢等等高級功能,因此可以簡單的實現(xiàn)類似ORM的功能。
首先,定義一個配置類,將數(shù)據(jù)庫字段和類型定義下來。按照常規(guī)的做法,從頁面字段到對象屬性到數(shù)據(jù)庫都應(yīng)該建立映射,這樣需要生成相應(yīng)的映射類。為簡單起見,我不使用POJO,而是使用Map作為數(shù)據(jù)的載體,key就是數(shù)據(jù)庫字段名,在頁面端我也用數(shù)據(jù)庫字段名作為域的名稱。這樣我直接通過名稱建立映射,犧牲了擴(kuò)展性和靈活性,但簡化了操作,也無需映射類,只要一個映射Map,key是數(shù)據(jù)庫字段名,value是我自己定義的字段類型。以后動態(tài)生成SQL和Map傳輸對象,以及從頁面request動態(tài)生成Map都是基于這個配置。
接下來,就很簡單了。先處理DAO的動態(tài)生成SQL代碼,下面是生成insertSQL的方法,

private static String generateInsertScoreSQL(Map m, String studentId)
{

StringBuffer sb = new StringBuffer();

StringBuffer sb2 = new StringBuffer();


sb.append("insert into STUDENT_SCORE (id,");

sb2.append(" values('");

sb2.append(studentId);

sb2.append("',");


for (Iterator iter = m.keySet().iterator(); iter.hasNext();)
{

String name = (String) iter.next();

String value = (String) m.get(name);

sb.append(name);

sb.append(",");


String type = (String) ScoreColumnMapping.scoreItemMap().get(name);


if (ScoreColumnMapping.INT.equalsIgnoreCase(type))
{


if (value == null || value.length() == 0)
{

sb2.append("null,");


} else
{

sb2.append(Integer.parseInt(value));

sb2.append(",");

}


} else if (ScoreColumnMapping.STRING.equalsIgnoreCase(type))
{

sb2.append("'");

sb2.append(value);

sb2.append("',");

}

}

sb.replace(sb.length() - 1, sb.length(), ")");

sb2.replace(sb2.length() - 1, sb2.length(), ")");

log.info(sb.toString() + sb2.toString());

return sb.toString() + sb2.toString();

}

生成
updateSQL的代碼:

private static String generateUpdateScoreSQL(Map m, String studentId)
{

StringBuffer sb = new StringBuffer();


sb.append("update STUDENT_SCORE set ");



for (Iterator iter = m.keySet().iterator(); iter.hasNext();)
{

String name = (String) iter.next();

String value = (String) m.get(name);

sb.append(name);

sb.append("=");


String type = (String) ScoreColumnMapping.scoreItemMap().get(name);


if (ScoreColumnMapping.INT.equalsIgnoreCase(type))
{


if (value == null || value.length() == 0)
{

sb.append("null,");


} else
{

sb.append(Integer.parseInt(value));

sb.append(",");

}


} else if (ScoreColumnMapping.STRING.equalsIgnoreCase(type))
{

sb.append("'");

sb.append(value);

sb.append("',");

}

}

sb.replace(sb.length() - 1, sb.length(), "");


sb.append(" where id='");

sb.append(studentId);

sb.append("'");


log.info(sb.toString());

return sb.toString();

}

從ResultSet中生成Map對象的代碼:

private static void getScoreFromRs(ResultSet rs, Map m) throws SQLException
{

String name;

String type;

for (Iterator iter = ScoreColumnMapping.scoreItemMap().keySet()


.iterator(); iter.hasNext();)
{

name = (String) iter.next();

type = (String) ScoreColumnMapping.scoreItemMap().get(name);


if (ScoreColumnMapping.INT.equalsIgnoreCase(type))
{

Object value = rs.getObject(name);

m.put(name, String.valueOf(value == null ? "" : value));


} else if (ScoreColumnMapping.STRING.equalsIgnoreCase(type))
{

String value = rs.getString(name);

m.put(name, value);

}

}


}

因為只有一個
DAO采用這種方式,所以我用private方法,這可以通過重構(gòu),將其抽取到Util類中,供所有DAO使用。頁面端也很簡單,我做個Façade類,它獲取request,并將其處理成一個Map,然后交給數(shù)據(jù)層處理(因為比較簡單,省去了業(yè)務(wù)層),代碼如下:
Map m = new HashMap();

for (Iterator iter = ScoreColumnMapping.scoreItemMap().keySet()


.iterator(); iter.hasNext();)
{

String name = (String) iter.next();

String value = request.getParameter(name);

……

m.put(name, value);

……

所有方法中未出現(xiàn)一個字段名稱,全部是從配置類中動態(tài)生成。這樣帶來了很多好處:
u 擴(kuò)展容易,如果需增加字段,無需更改核心代碼,只要修改配置文件和數(shù)據(jù)庫表定義,然后頁面上加加域
u 開發(fā)效率提高,整個核心代碼,幾個小時搞定,然后就是處理表單域間的關(guān)系和顯示邏輯,DAO層快速開發(fā),而且基本減少出錯的可能性(出錯也是配置文件錯)。
u 排錯容易,我定義了清晰的Exception機(jī)制和log機(jī)制,出錯只記錄一次,并通過log.info記錄生成的SQL語句,很容易根據(jù)這些信息查出錯誤原因并解決。
呵呵,其實很多東西并不如我們想象的那么復(fù)雜,看東西要看其本質(zhì)。普元不是提出什么面向構(gòu)件,xml數(shù)據(jù)總線的理論嗎?它直接傳xml文件?還不是用Map做載體,然后動態(tài)生成SQL語句。我這個東西擴(kuò)展出去,定義完善的xml配置,然后多幾層封裝,然后加上些企業(yè)特性,也能出去吹吹的。
但這也是權(quán)宜之計,以后我還是會使用hibernate,而不是把這個東西做完善。不重新發(fā)明輪子,這也是open source社區(qū)的共識。但自己做做也挺有意義的,起碼能培養(yǎng)自己的創(chuàng)造力,也能對技術(shù)實質(zhì)有第一手的了解,而且很開心。
做點(diǎn)東西,學(xué)點(diǎn)技術(shù),然后進(jìn)行些反思和總結(jié),這是我一貫的做法。也希望能對大家有所啟發(fā)。