自己實(shí)現(xiàn)ORM

          這篇文章源自剛開發(fā)的一個(gè)小項(xiàng)目。項(xiàng)目中并未使用hibernate,但我還是要把它放在hibernate欄下。理由很簡單,技術(shù)是死的,而人是活的,能熟練的使用一項(xiàng)技術(shù)不算什么,但能恰當(dāng)?shù)倪x擇相應(yīng)的技術(shù),甚至自己想出辦法來優(yōu)雅的解決實(shí)際問題,就需要一定的積累了。而這種積累就來源自項(xiàng)目實(shí)踐和對(duì)各種技術(shù)其實(shí)質(zhì)的理解。我記得在某個(gè)論壇上某人(名字忘了)說過一句話:如果學(xué)習(xí)hibernate只是學(xué)會(huì)了怎么mapping,怎么寫DAO,就只是學(xué)了皮毛,學(xué)hibernate就要從中了解到很多持久層的最佳實(shí)踐。我深以為然!

          項(xiàng)目很簡單,頁面也不多,但頁面字段較多(100多個(gè)),相互間還有一定關(guān)聯(lián)。而且存入數(shù)據(jù)庫類型多樣,有varchar, integerdate幾種。我是很希望用hibernate來實(shí)現(xiàn)的,但考慮到項(xiàng)目時(shí)間較緊,而我對(duì)hibernate的了解還是停留在理論和學(xué)習(xí)階段(有點(diǎn)落后啊!),采用不熟悉的技術(shù)項(xiàng)目風(fēng)險(xiǎn)較大,所以還是使用普通JDBC作為持久層方案。面對(duì)這么多字段,一個(gè)個(gè)去拼SQL語句,代碼臃腫,而且容易出錯(cuò),也難以維護(hù)。我得對(duì)這幾種類型的字段,在插入、更新和讀取時(shí)分別處理,寫一個(gè)private方法,傳入類型和字段名,讀取相應(yīng)的ResultSet,是不錯(cuò)的方法,至少是比較優(yōu)雅的實(shí)現(xiàn)。

          什么是優(yōu)雅?動(dòng)態(tài)設(shè)置,避免hardcode,就是優(yōu)雅;層次清晰,層次間耦合最低,就是優(yōu)雅;只寫一處,處處引用,就是優(yōu)雅;代碼精煉,避免過度設(shè)計(jì),就是優(yōu)雅;接口明確,調(diào)用簡單,就是優(yōu)雅;調(diào)試容易,便于測試,就是優(yōu)雅。。。。。。而優(yōu)雅的設(shè)計(jì)和實(shí)現(xiàn),在可擴(kuò)展性、可維護(hù)性、開發(fā)效率、開發(fā)成本等方面都是最好的。

          Hibernate就是優(yōu)雅的設(shè)計(jì),它通過配置文件,建立實(shí)體與數(shù)據(jù)庫的映射,動(dòng)態(tài)的生成SQL語句,避免了對(duì)屬性字段的hardcode,這就是它最本質(zhì)的思想。我不使用hibernate,但一樣可以借鑒它的思想,我不需要對(duì)象容器、分頁查詢等等高級(jí)功能,因此可以簡單的實(shí)現(xiàn)類似ORM的功能。

          首先,定義一個(gè)配置類,將數(shù)據(jù)庫字段和類型定義下來。按照常規(guī)的做法,從頁面字段到對(duì)象屬性到數(shù)據(jù)庫都應(yīng)該建立映射,這樣需要生成相應(yīng)的映射類。為簡單起見,我不使用POJO,而是使用Map作為數(shù)據(jù)的載體,key就是數(shù)據(jù)庫字段名,在頁面端我也用數(shù)據(jù)庫字段名作為域的名稱。這樣我直接通過名稱建立映射,犧牲了擴(kuò)展性和靈活性,但簡化了操作,也無需映射類,只要一個(gè)映射Mapkey是數(shù)據(jù)庫字段名,value是我自己定義的字段類型。以后動(dòng)態(tài)生成SQLMap傳輸對(duì)象,以及從頁面request動(dòng)態(tài)生成Map都是基于這個(gè)配置。

          接下來,就很簡單了。先處理DAO的動(dòng)態(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對(duì)象的代碼:

          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);

                               }


                        }


           

                 }

          因?yàn)橹挥幸粋€(gè)DAO采用這種方式,所以我用private方法,這可以通過重構(gòu),將其抽取到Util類中,供所有DAO使用。頁面端也很簡單,我做個(gè)Fa?ade類,它獲取request,并將其處理成一個(gè)Map,然后交給數(shù)據(jù)層處理(因?yàn)楸容^簡單,省去了業(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)一個(gè)字段名稱,全部是從配置類中動(dòng)態(tài)生成。這樣帶來了很多好處:

          u       擴(kuò)展容易,如果需增加字段,無需更改核心代碼,只要修改配置文件和數(shù)據(jù)庫表定義,然后頁面上加加域

          u       開發(fā)效率提高,整個(gè)核心代碼,幾個(gè)小時(shí)搞定,然后就是處理表單域間的關(guān)系和顯示邏輯,DAO層快速開發(fā),而且基本減少出錯(cuò)的可能性(出錯(cuò)也是配置文件錯(cuò))。

          u       排錯(cuò)容易,我定義了清晰的Exception機(jī)制和log機(jī)制,出錯(cuò)只記錄一次,并通過log.info記錄生成的SQL語句,很容易根據(jù)這些信息查出錯(cuò)誤原因并解決。

           

          呵呵,其實(shí)很多東西并不如我們想象的那么復(fù)雜,看東西要看其本質(zhì)。普元不是提出什么面向構(gòu)件,xml數(shù)據(jù)總線的理論嗎?它直接傳xml文件?還不是用Map做載體,然后動(dòng)態(tài)生成SQL語句。我這個(gè)東西擴(kuò)展出去,定義完善的xml配置,然后多幾層封裝,然后加上些企業(yè)特性,也能出去吹吹的。

          但這也是權(quán)宜之計(jì),以后我還是會(huì)使用hibernate,而不是把這個(gè)東西做完善。不重新發(fā)明輪子,這也是open source社區(qū)的共識(shí)。但自己做做也挺有意義的,起碼能培養(yǎng)自己的創(chuàng)造力,也能對(duì)技術(shù)實(shí)質(zhì)有第一手的了解,而且很開心。

          做點(diǎn)東西,學(xué)點(diǎn)技術(shù),然后進(jìn)行些反思和總結(jié),這是我一貫的做法。也希望能對(duì)大家有所啟發(fā)。

          posted on 2005-10-25 13:55 pesome 閱讀(2580) 評(píng)論(2)  編輯  收藏 所屬分類: Spring+Struts+Hibernate

          評(píng)論

          # re: 自己實(shí)現(xiàn)ORM 2005-10-26 23:41 BlueO2

          JSTL有直接resultset轉(zhuǎn)化為result的類 而result可轉(zhuǎn)化為sortedMap 你又可以省個(gè)輪子啦 Hoho 而且比你的還rounder
          不過這個(gè)似乎不是ORM ORM為了以oo的方式來操作數(shù)據(jù)庫,目前僅僅是簡單但是很長的sql得封裝而已。就是個(gè)應(yīng)用DAO模式的持久層,離ORM還差好遠(yuǎn)  回復(fù)  更多評(píng)論   

          # re: 自己實(shí)現(xiàn)ORM 2005-10-27 09:36 zhangjun

          呵呵,的確不算ORM,頂多是MRM(Map Relation Mapping)。但對(duì)于小項(xiàng)目,特別是沒有什么業(yè)務(wù)邏輯的項(xiàng)目,還是挺方便的。也不需要什么對(duì)象,直接用map就搞定,put,get用的也簡單,還省得用什么reflaction或CGILIB。適合就是最好的,至少我這個(gè)項(xiàng)目用的很爽。也謝謝指教,大家討論嘛!  回復(fù)  更多評(píng)論   

          <2005年10月>
          2526272829301
          2345678
          9101112131415
          16171819202122
          23242526272829
          303112345

          導(dǎo)航

          統(tǒng)計(jì)

          公告

          主要記錄作者在學(xué)習(xí)java中的每一步足跡。除非特別說明,所有文章均為本blog作者原創(chuàng),如需轉(zhuǎn)載請注明出處和原作者,如用于商業(yè)目的,需跟作者本人聯(lián)系。
          歡迎大家訪問:

          常用鏈接

          留言簿(16)

          隨筆分類

          隨筆檔案

          文章分類

          文章檔案

          相冊

          收藏夾

          java技術(shù)

          人間百態(tài)

          朋友們的blog

          搜索

          最新評(píng)論

          閱讀排行榜

          評(píng)論排行榜

          主站蜘蛛池模板: 泸溪县| 吉木乃县| 汝阳县| 体育| 湖口县| 久治县| 达孜县| 凯里市| 札达县| 安丘市| 祁阳县| 葫芦岛市| 宿迁市| 平罗县| 清镇市| 获嘉县| 新河县| 赤水市| 镶黄旗| 广宁县| 太原市| 衡东县| 大关县| 宁城县| 寿宁县| 筠连县| 阿拉善左旗| 绥阳县| 定兴县| 呼玛县| 临沂市| 伊川县| 衡南县| 龙门县| 长汀县| 津南区| 泾川县| 建始县| 黎城县| 黄陵县| 垫江县|