這是最后一篇Java雜談了,以O(shè)RM框架的談?wù)撌瘴?,也算是把J2ee的最后一方面給涵蓋到了,之所以這么晚才總結(jié)出ORM這方面,一是筆者這兩周比較忙,另一方面也想善始善終,仔細(xì)的先自己好好研究一下ORM框架技術(shù),不想草率的敷衍了事。  

        其實(shí)J2ee的規(guī)范指南里面就已經(jīng)包括了一些對(duì)象持久化技術(shù),例如JDO(Java       Data       Object)就是Java對(duì)象持久化的新規(guī)范,一個(gè)用于存取某種數(shù)據(jù)倉(cāng)庫(kù)中的對(duì)象的標(biāo)準(zhǔn)化API,提供了透明的對(duì)象存儲(chǔ),對(duì)開(kāi)發(fā)人員來(lái)說(shuō),存儲(chǔ)數(shù)據(jù)對(duì)象完全不需要額外的代碼(如JDBC       API的使用)。這些繁瑣的工作已經(jīng)轉(zhuǎn)移到JDO產(chǎn)品提供商身上,使開(kāi)發(fā)人員解脫出來(lái),從而集中時(shí)間和精力在業(yè)務(wù)邏輯上。另外,JDO很靈活,因?yàn)樗梢栽谌魏螖?shù)據(jù)底層上運(yùn)行。JDBC只是面向關(guān)系數(shù)據(jù)庫(kù)(RDBMS)JDO更通用,提供到任何數(shù)據(jù)底層的存儲(chǔ)功能,比如關(guān)系數(shù)據(jù)庫(kù)、文件、XML以及對(duì)象數(shù)據(jù)庫(kù)(ODBMS)等等,使得應(yīng)用可移植性更強(qiáng)。我們?nèi)绻斫鈱?duì)象持久化技術(shù),首先要問(wèn)自己一個(gè)問(wèn)題:為什么傳統(tǒng)的JDBC來(lái)持久化不再能滿足大家的需求了呢?  

        筆者認(rèn)為最好是能用JDBC真正編寫(xiě)過(guò)程序了才能真正體會(huì)ORM的好處,同樣的道理,真正拿Servlet/Jsp做過(guò)項(xiàng)目了才能體會(huì)到Struts、 Spring等框架的方便之處。很幸運(yùn)的是筆者這兩者都曾經(jīng)經(jīng)歷過(guò),用混亂的內(nèi)嵌Java代碼的Jsp加Servlet轉(zhuǎn)發(fā)寫(xiě)過(guò)完整的Web項(xiàng)目,也用 JDBC搭建過(guò)一個(gè)完整C/S項(xiàng)目的后臺(tái)。所以現(xiàn)在接觸到新框架才更能體會(huì)它們思想和實(shí)現(xiàn)的優(yōu)越之處,回顧從前的代碼,真是丑陋不堪啊。^_^  

        回到正題,我們來(lái)研究一下為什么要從JDBC發(fā)展到ORM。簡(jiǎn)單來(lái)說(shuō),傳統(tǒng)的JDBC要花大量的重復(fù)代碼在初始化數(shù)據(jù)庫(kù)連接上,每次增刪改查都要獲得 Connection對(duì)象,初始化Statement,執(zhí)行得到ResultSet再封裝成自己的List或者Object,這樣造成了在每個(gè)數(shù)據(jù)訪問(wèn)方法中都含有大量冗余重復(fù)的代碼,考慮到安全性的話,還要加上大量的事務(wù)控制和log記錄。雖然我們學(xué)習(xí)了設(shè)計(jì)模式之后,可以自己定義Factory來(lái)幫助減少一部分重復(fù)的代碼,但是仍然無(wú)法避免冗余的問(wèn)題。其次,隨著OO思想深入人心,連典型的過(guò)程化語(yǔ)言Perl等都冠冕堂皇的加上了OO的外殼,何況是 Java中繁雜的數(shù)據(jù)庫(kù)訪問(wèn)持久化技術(shù)呢?強(qiáng)調(diào)面向?qū)ο缶幊痰慕Y(jié)果就是找到一個(gè)橋梁,使得關(guān)系型數(shù)據(jù)庫(kù)存儲(chǔ)的數(shù)據(jù)能準(zhǔn)確的映射到Java的對(duì)象上,然后針對(duì)Java對(duì)象來(lái)設(shè)計(jì)對(duì)象和方法,如果我們把數(shù)據(jù)庫(kù)的Table當(dāng)作Class,Record當(dāng)作Instance的話,就可以完全用面向?qū)ο蟮乃枷雭?lái)編寫(xiě)數(shù)據(jù)層的代碼。于是乎,Object       Relationship       Mapping的概念開(kāi)始普遍受到重視,盡管很早很早就已經(jīng)有人提出來(lái)了。  

        缺點(diǎn)我們已經(jīng)大概清楚了,那么如何改進(jìn)呢?對(duì)癥下藥,首先我們要解決的是如何從Data       Schema準(zhǔn)備完美的映射到Object       Schema,另外要提供對(duì)數(shù)據(jù)庫(kù)連接對(duì)象生命周期的管理,對(duì)事務(wù)不同粒度的控制和考慮到擴(kuò)展性后提供對(duì)XML、Properties等可配置化的文件的支持。到目前為止,有很多框架和技術(shù)在嘗試著這樣做。例如似乎是封裝管理得過(guò)了頭的EJB、很早就出現(xiàn)目前已經(jīng)不在開(kāi)發(fā)和升級(jí)了的Apache       OJB、首先支持Manual       SQL的iBATIS,還有公認(rèn)非常優(yōu)秀的Hibernate等等。在分別介紹它們之前,我還想反復(fù)強(qiáng)調(diào)這些框架都在試圖做什么:  

        畢竟Java       Object和數(shù)據(jù)庫(kù)的每一條Record還是有很大的區(qū)別,就是類型上來(lái)說(shuō),DB是沒(méi)有Boolean類型的。而Java也不得不用封裝類(Integer、Double等)為了能映射上數(shù)據(jù)庫(kù)中為null的情況,畢竟Primitive類型是沒(méi)有null值的。還有一個(gè)比較明顯的問(wèn)題是,數(shù)據(jù)庫(kù)有主鍵和外鍵,而Java中仍然只能通過(guò)基本類型來(lái)對(duì)應(yīng)字段值而已,無(wú)法規(guī)定Unique等特征,更別提外鍵約束、事務(wù)控制和級(jí)聯(lián)操作了。另外,通過(guò)Java       Object預(yù)設(shè)某Field值去取數(shù)據(jù)庫(kù)記錄,是否在這樣的記錄也是不能保證的。真的要設(shè)計(jì)到完全映射的話,Java的Static被所有對(duì)象共享的變量怎么辦?在數(shù)據(jù)庫(kù)中如何表現(xiàn)出來(lái)……  
我們能看到大量的問(wèn)題像一座座大山橫在那些框架設(shè)計(jì)者們面前,他們并不是沒(méi)有解決辦法,而是從不同的角度去考慮,會(huì)得到很多不同的解決方案,問(wèn)題是應(yīng)該采取哪一種呢?甚至只有等到真正設(shè)計(jì)出來(lái)了投入生產(chǎn)使用了,才能印證出當(dāng)初的設(shè)想是否真的能為項(xiàng)目開(kāi)發(fā)帶來(lái)更多的益處。筆者引用一份文檔中提到一個(gè)健壯的持久化框架應(yīng)該具有的特點(diǎn):  
        A       robust       persistence       layer       should       support----  
        1.   Several       types       of       persistence       mechanism  
        2.   Full       encapsulation       of       the       persistence       mechanism.  
        3.   Multi-object       actions  
        4.   Transactions       Control  
        5.   Extensibility  
        6.   Object       identifiers  
        7.   Cursors:       logical       connection       to       the       persistence       mechanism  
        8.   Proxies:       commonly       used       when       the       results       of       a       query       are       to       be       displayed       in       a       list  
        9.   Records:     avoid     the     overhead       of       converting       database       records       to       objects       and       then       back       to       records  
        10.   Multi       architecture  
        11.   Various       database       version       and/or       vendors  
        12.   Multiple       connections  
        13.   Native       and       non-native       drivers  
        14.   Structured       query       language       queries(SQL)  

        現(xiàn)在來(lái)簡(jiǎn)短的介紹一下筆者用過(guò)的一些持久化框架和技術(shù),之所以前面強(qiáng)調(diào)那么多共通的知識(shí),是希望大家不要盲從流行框架,一定要把握它的本質(zhì)和卓越的思想好在哪里。  
        1.   Apache       OJB  
        OJB代表Apache       Object       Relational       Bridge,是Apache開(kāi)發(fā)的一個(gè)數(shù)據(jù)庫(kù)持久型框架。它是基于J2ee規(guī)范指南下的持久型框架技術(shù)而設(shè)計(jì)開(kāi)發(fā)的,例如實(shí)現(xiàn)了ODMG       3.0規(guī)范的API,實(shí)現(xiàn)了JDO規(guī)范的API,       核心實(shí)現(xiàn)是Persistence       Broker       API。OJB使用XML文件來(lái)實(shí)現(xiàn)映射并動(dòng)態(tài)的在Metadata       layer聽(tīng)過(guò)一個(gè)Meta-Object-Protocol(MOP)來(lái)改變底層數(shù)據(jù)的行為。更高級(jí)的特點(diǎn)包括對(duì)象緩存機(jī)制、鎖管理機(jī)制、 Virtual       代理、事務(wù)隔離性級(jí)別等等。舉個(gè)OJB       Mapping的簡(jiǎn)單例子ojb-repository.xml:  

        <class-descriptor       class=”com.ant.Employee”       table=”EMPLOYEE”>  
                <field-descriptor       name=”id”       column=”ID”      
                    jdbc-type=”INTEGER”       primarykey=”true”       autoincrement=”true”/>  

                <field-descriptor       name=”name”       column=”NAME”       jdbc-type=”VARCHAR”/>  
      </class-descrptor>  

      <class-descriptor       class=”com.ant.Executive”       table=”EXECUTIVE”>  
                <field-descriptor       name=”id”       column=”ID”      
                jdbc-type=”INTEGER”       primarykey=”true”       autoincrement=”true”/>  

                <field-descriptor       name=”department”       column=”DEPARTMENT”       jdbc-type=”VARCHAR”/>  
                               
                <reference-descriptor       name=”super”       class-ref=”com.ant.Employee”>  
                        <foreignkey       field-ref=”id”/>  
                </reference-descriptor>  
      </class-descrptor>  

      2.   iBATIS  
      iBATIS最大的特點(diǎn)就是允許用戶自己定義SQL來(lái)組配Bean的屬性。因?yàn)樗腟QL語(yǔ)句是直接寫(xiě)入XML文件中去的,所以可以最大程度上利用到 SQL語(yǔ)法本身能控制的全部特性,同時(shí)也能允許你使用特定數(shù)據(jù)庫(kù)服務(wù)器的額外特性,并不局限于類似SQL92這樣的標(biāo)準(zhǔn),它最大的缺點(diǎn)是不支持枚舉類型的持久化,即把枚舉類型的幾個(gè)對(duì)象屬性拼成與數(shù)據(jù)庫(kù)一個(gè)字段例如VARCHAR對(duì)應(yīng)的行為。這里也舉一個(gè)Mapping文件的例子sqlMap.xml:  
        <sqlMap>  
            <typeAlias       type=”com.ant.Test”       alias=”test”/>  

            <resultMap       class=”test”       id=”result”>  
                    <result       property=”testId”       column=”TestId”/>  
                    <result       property=”name”       column=”Name”/>  
                    <result       property=”date”       column=”Date”/>  
            </resultMap>  

            <select       id=”getTestById”       resultMap=”result”       parameterClass=”int”>  
                select       *       from       Test       where       TestId=#value#  
            </select>  

            <update       id=”updateTest”       parameterClass=”test”>  
                Update       Tests       set       Name=#name#,       Date=”date”       where       TestId=#testId#  
            </update>  
        </sqlMap>  

      3.   Hibernate  
      Hibernate無(wú)疑是應(yīng)用最廣泛最受歡迎的持久型框架,它生成的SQL語(yǔ)句是非常優(yōu)秀。雖然一度因?yàn)椴荒苤С质止QL而性能受到局限,但隨著新一代 Hibernate       3.x推出,很多缺點(diǎn)都被改進(jìn),Hibernate也因此變得更加通用而時(shí)尚。同樣先看一個(gè)Mapping文件的例子customer.hbm.xml來(lái)有一個(gè)大概印象:  

      <hibernate-mapping>  
              <class       name=”com.ant.Customer”       table=”Customers”>  
              <id       name=”customerId”       column=”CustomerId”       type=”int”       unsaved-value=”0”>  
                      <generator       class=”sequence”>  
                              <param       name=”sequence”>   Customers_CustomerId_Seq   </param>  
                      </generator>  
            </id>  

            <property       name=”firstName”       column=”FirstName”/>  
            <property       name=”lastName”       column=”LastName”/>  

          <set       name=”addresses”       outer-join=”true”>  
                  <key       column=”Customer”/>  
                          <one-to-many       class=”com.ant.Address”/>  
          </set>  
 
          …  
            </class>

    </hibernate-mapping>  

        Hibernate有很多顯著的特性,最突出的就是它有自己的查詢語(yǔ)言叫做HQL,在HQL中select       from的不是Table而是類名,一方面更加面向?qū)ο?,另外一方面通過(guò)在hibernate.cfg.xml中配置Dialect為HQL可以使得整個(gè)后臺(tái)與數(shù)據(jù)庫(kù)脫離耦合,因?yàn)椴还苡媚欠N數(shù)據(jù)庫(kù)我都是基于HQL來(lái)查詢,Hibernate框架負(fù)責(zé)幫我最終轉(zhuǎn)換成特定數(shù)據(jù)庫(kù)里的SQL語(yǔ)句。另外 Hibernate在Object-Caching這方面也做得相當(dāng)出色,它同時(shí)管理兩個(gè)級(jí)別的緩存,當(dāng)數(shù)據(jù)被第一次取出后,真正使用的時(shí)候?qū)ο蟊环旁谝患?jí)緩存管理,這個(gè)時(shí)候任何改動(dòng)都會(huì)影響到數(shù)據(jù)庫(kù);而空閑時(shí)候會(huì)把對(duì)象放在二級(jí)緩存管理,雖然這個(gè)時(shí)候與數(shù)據(jù)庫(kù)字段能對(duì)應(yīng)上但未綁定在一起,改動(dòng)不會(huì)影響到數(shù)據(jù)庫(kù)的記錄,主要目的是為了在重復(fù)讀取的時(shí)候更快的拿到數(shù)據(jù)而不用再次請(qǐng)求連接對(duì)象。其實(shí)關(guān)于這種緩存的設(shè)計(jì)建議大家研究一下Oracle的存儲(chǔ)機(jī)制(原理是相通的),Oracle犧牲了空間換來(lái)時(shí)間依賴于很健壯的緩存算法來(lái)保證最優(yōu)的企業(yè)級(jí)數(shù)據(jù)庫(kù)訪問(wèn)速率。  

        以上是一些Mapping的例子,真正在Java代碼中使用多半是繼承各個(gè)框架中默認(rèn)的Dao實(shí)現(xiàn)類,然后可以通過(guò)Id來(lái)查找對(duì)象,或者通過(guò) Example來(lái)查找,更流行的是更具Criteria查找對(duì)象。Criteria是完全封裝了SQL條件查詢語(yǔ)法的一個(gè)工具類,任何一個(gè)查詢條件都可以在Criteria中找到方法與之對(duì)應(yīng),這樣可以在Java代碼級(jí)別實(shí)現(xiàn)SQL的完全控制。另外,現(xiàn)在許多ORM框架的最新版本隨著JDk       5.0加入Annotation特性都開(kāi)始支持用XDoclet來(lái)自動(dòng)根據(jù)Annotation來(lái)生成XML配置文件了。  

        筆者不可能詳細(xì)的講解每一個(gè)框架,也許更多的人在用Hibernate,筆者是從OJB開(kāi)始接觸ORM技術(shù)的,它很原始卻更容易讓人理解從JDBC到 ORM的過(guò)渡。更多的細(xì)節(jié)是可以從官方文檔和書(shū)籍中學(xué)到的,但我們應(yīng)該更加看中它們?cè)O(shè)計(jì)思想的來(lái)源和閃光點(diǎn),不是盲從它們的使用方法。