1 Java與數據庫應用,JDBC
Java發明以來,在短短的幾年之間,迅速占領了從桌面應用(J2SE)到服務器(J2EE),再到小型設備嵌入式系統(J2ME)的應用開發市場,其語言吸取了SmallTalk的一切皆對象的理念,擺脫了C++的歷史累贅,簡潔、自由的風格贏得了很多開發者的喜愛。從JDK1.1開始,Java成為實用的語言,而不是被人觀望的新品秀;再經過JDK1.2的大量增強(尤其是Collection Framework),JDK1.3的虛擬機效率提升(HotSpot),JDK1.4的融合百家之長(Logging、RegExp、NewIO等),現在已經是成熟穩重,頗顯大家風范。
在企業級市場上,大部分的應用建立在數據庫基礎上,數據是企業的生命,傳統開發語言,包括面向過程的C、面向對象的C++、變種Pascal的Delphi(非常棒的語言,我用過四年),面向數據的PowerBuilder等等,先後在數據庫開發的舞台上展現風姿。Java當然不會放過這些,於是,出現了JDBC。在JDBC的幫助下,Java也迅速滲入數據庫開發的市場,尤其是面向企業服務器的應用開發。
今天要談的JDO,與JDBC有非常密切的關系,盡管JDO並不是只面向JDBC的數據對象包裝規范。下面先簡單地介紹一下JDBC。
1.1 關系數據庫之百家爭鳴,ODBC
關系數據庫的歷史一言難盡,我只能從我的接觸經歷和所見所聞,簡單地敘述一下。最早的時候,計算機還只在一些大型的研究機關露面,並不是普羅大眾可以涉及的。蘋果電腦將個人電腦引入民間,再隨著IBM的PC標准開放,個人電腦逐步普及開來,加上微軟的DOS操作系統,以及Borland的Turbo系列語言開發環境,老百姓發現原來電腦可以做這麼多事!後來,出現了DBASE,一個簡單的關系數據庫系統,和SQL語言。後來,Borland看到了數據庫的市場前景,推出了Paradox(也是當今Delphi和C++Builder中仍然使用的Paradox),一舉占領了民用數據庫的大部分江山,之後,Borland干脆收購了Dbase,後來又購買了InterBase,將數據庫市場的領先優勢一直保持到Windows3.0出現。這時候,微軟在Windows1.0和2.0被人痛罵之後頑強地推出3.0,以及更穩定的3.1和Win32API,造就了個人電腦桌面操作系統的霸主地位,在Borland未警覺的情況下,購買了同樣具有類Dbase數據庫技術的Fox公司,並迅速將其易用化,形成了FoxBase,後來演變成FoxPro,逐漸超過了Borland,成為個人電腦數據庫的大戶。微軟再接再勵,為簡單易用而低負荷要求的數據庫應用開發了Access,贏得了廣大開發人員的心。當然,同期的Oracle、Sybase、Informix等商用數據庫憑專注於企業級數據庫技術成為高端的幾位領軍人物。微軟當然也想成為高端數據庫供應商之一,於是自行開發一套面向企業級應用的數據庫,不過很快項目夭折,微軟不甘心,購買了Sybase的底層TDS技術,包裝成了SQL Server,憑微軟的高度易用性的特點,也占領了不少市場。
當市場上出現眾多的數據庫產品之後,Borland和微軟都發現自己擁有的數據庫產品挺多,市場也不小,不同的產品給用戶帶來不同的配置任務,不利於所有產品的推廣,於是,兩者紛紛開始制定數據庫訪問的規范,微軟推出了ODBC,其面向開發人員的親和性,逐步獲得了認可,同時,Borland糾集了IBM和Novell也推出了IDAPI數據庫接口規范,也就是今天BDE的核心,不過後來Novell和IBM先後退出,只剩Borland獨力支撐。不過Borland是一個技術實力雄厚的公司,其技術一向領先於微軟,BDE的性能比初期的ODBC不知道要好多少倍,後來微軟偷師學藝,把連接池等技術加到ODBC中,在Delphi3.0及其BDE在市場上風光無限的時候,逐步趕了上來並有超過。直到今天,BDE仍是Borland的產品線上的數據庫訪問標准,而微軟如果不是將ODBC和多數數據庫的客戶端內嵌進Windows的話,估計BDE仍是市場的贏家。不過,微軟是玩弄市場的老手,通過對操作系統的壟斷,其數據庫產品和ODBC標准終究占據了多數開發市場。
1.2 從optional pack到JDK的標准API
Java開始涉及數據庫應用後,Sun就極力制定Java的數據庫規范,JDBC API就是類似ODBC一樣,對數據庫訪問的底層協議進行最基本的包裝,然後形成一套統一的數據訪問接口,數據庫連接、SQL語句句柄、結果集,都帶有ODBC的影子。以方便配置為目的,Sun極力推薦完全瘦客戶端的TYPE 4型JDBC驅動,這是一個不需要安裝數據庫客戶端的驅動規范,是現在使用最多的。當然,為了保持與舊的數據庫兼容,JDBC規范中包括了專用於連接ODBC的TYPE 1驅動和需要安裝數據庫客戶端的TYPE 2驅動,以及可以由廠商在數據庫服務端專門提供面向JDBC的服務的TYPE 3驅動。
JDBC最早出現時,還不屬於標准JDK的一部分,而是作為一個額外包提供下載。後來,隨著Java編寫的數據庫應用的的增多,和JDBC規范本身的逐漸成熟,JDBC終於成為JDK1.1的一部分。
JDBC目前最新的是3.0版本,還有正在討論中的4.0版本。實際上,在開發中使用得最多的還是1.0中的API,2.0中主要增加了可雙向滾動的結果集、更新批處理等提高可用性和性能的API,3.0主要增加了連接池、可更新的結果集等特性。4.0將在可管理性、連接池規范化等方面再做改進。
2 面向對象與數據庫
現在的程序員,沒有不知道面向對象的。作為接近真實客觀世界的開發概念,面向對象使程序代碼更易讀、設計更合理。在普遍存在的數據庫應用領域,開發人員對面向對象的追求從未停止過。從八十年代開始,就有很多公司和研究機構在進行著面向對象與數據庫結合的研究。
2.1 SmallTalk、C與C++、Delphi-Object Pascal、Java
面向對象的語言最早有好幾種雛形,IBM的SmallTalk是其中最為流行的,在SmallTalk中,一切都是對象,一切都是類,它將面向對象的概念發揮到了極致。面向對象的編程比起傳統的面向過程的方式挺進了一大步,使人們認識到:原來軟件可以這樣寫。不過,由於計算機基本結構與底層硬件體系和系統軟件的限制,SmallTalk還不能在理想的性能前提下推廣到普通的應用上,這一點暫時限制了SmallTalk的發展,接著,C語言的面向對象版C++出現了,由於使用C語言的人很多,C++很快成為面向對象編程的主流語言。不過,為了保證與C的兼容,C++保留了很多面向過程的痕跡,比如惡心的指針、全局變量等等。Pascal的改進版Object Pascal相對來說安全許多,後來Borland干脆將Object Pascal換了個名字,叫Delphi,從此開創了一片面向對象編程的新世界, Delphi的嚴謹語法和快速編譯吸引了眾多的應用開發者,加上Borland的完美的VCL組件體系,比起MFC來方便而容易,另外,Delphi完整的數據庫組件,也將數據庫開發變得簡單而容易,Delphi再次成為成熟的面向對象開發語言。微軟當然不會放過這些,通過將MFC內置到操作系統中,微軟的VC++也搶回一些市場。這也是為什麼Delphi開發的應用程序編譯後會比VC、VB開發的程序大的原因。
1995年,Sun的一個開發小組本來為了小型嵌入式系統開發OAK語言,結果無心插柳柳成蔭,發展出了Java語言,它是一個完全擺脫了傳統語言的各種負擔的面向對象的語言,當然,也保留了一些非面向對象的核心(原始類型)以保證速度。現在Java也為最流行的面向對象語言之一。當然,微軟同樣不會放過它,擅於模仿的微軟立即弄出一個C#來與之競爭,並在C#中保留了一些變種的指針(指代)以吸引傳統的C開發者。關於這些語言的各自特點,這裡就不一一贅述了。
2.2 數據庫與數據對象化
數據庫是企業級應用不可缺少的,因此,在面向對象流行的時候,數據庫廠商也在進行著數據對象化的研究。這些研究在上個世紀八十年代就初現端倪。
數據庫的對象化一般有兩個方向:一個是在主流的關系數據庫的基礎上加入對象化特征,使之提供面向對象的服務,但訪問語言還是基於SQL;另一個方向就是徹底拋棄關系數據庫,用全新的面向對象的概念來設計數據庫,這就是對象數據庫ODBMS。
2.2.1 關系數據庫對象化、SQL99與JDBC3.0
隨著許多關系數據庫廠商開始提供對象化服務,各自的接口開始互不兼容,在經歷一些麻煩之後,關系數據庫廠商感覺到規范化的必要,因為當初關系數據庫雄霸天下時SQL92標准起了很大作用,大家可以按照統一的編程方式來訪問高性能的商用數據庫。
關系數據庫廠商集中起來,重新將對象化服務規范起來,形成了SQL99規范,將其中的對象結構等內容規范起來,開始一個嶄新的面向對象的關系數據庫(ORDBMS)的歷程。
JDBC3.0就是在這種情況下出台的,它將對關系數據庫中的對象服務的訪問API規范起來,為Java平台提供了訪問ORDBMS的標准方式。當然,JDBC3.0對傳統的SQL操作也進行了很多功能增強。
Oracle是一個傳統的關系數據庫廠商,在對象化的道路上,Oracle當然采取追加對象化特征的道路,以侵入數據對象化的市場,保持Oracle在數據庫領域的領導地位。如果說Oracle7.4使Oracle走向全盛的話,從Oracle8開始,Oracle就成為關系數據庫加對象類型的先驅。在Oracle8中,我們可以定義一些數據結構(Record),將普通的類型包裝在其中成為數據元素,然後可以在客戶端按Record結構進行訪問,初步提供了面向對象的數據庫服務。
2.2.2 對象數據庫
對象數據庫就是采用全新的面向對象概念來設計數據庫的全新數據庫類型。在這方面,主要以一些大學研究機構進行設計和開發,有些也形成了產品,不過由於市場方面的原因(主要是關系數據庫的容易上手和市場絕對領導地位)和ODBMS先天的一些弱點(比如查詢引擎很難優化),使ODBMS沒有象關系數據庫那樣流行起來。
不過對象數據庫的對象化特點還是令人割捨不下,目前還是有一些很好的產品在市場上,從商用的到免費的都用。目前在ODBMS領域占據領導地位的是Versant、FastObjects和ObjectStore等幾大廠商,並且,市場份額也在逐步擴展。免費的產品包括C++編寫的Ozone、純Java的db4o等等。還有一些研究機構開發一些底層的面向對象數據庫引擎,但只提供一些底層的API,不提供管理方面的功能,以及一些算法提供開放式接口,讓廠商去選擇和實現。比如美國威斯康新大學計算機系數據庫組的SHORE引擎,就是一個非常出色的面向對象數據庫引擎,現在還在積極的更新中,一些其它研究機構和數據庫廠商采用它完成了自己的特別的對象數據庫,比如專用於地理信息的數據庫、專用於宇宙空間數據研究的數據庫等等。
目前對象數據庫最大的障礙是缺乏統一的規范,各個數據庫廠商有各自的訪問接口。對象數據庫比起關系數據庫來,不只是基本的幾種數據類型那麼簡單,它還涉及繼承處理、多態等一大堆面向對象特征的實現,規范化道路當然困難重重。這也是對象數據庫無法普及的一個重要原因。
也有一些機構提出了一些建議的規范,比如制定Corba標准的OMG小組的一個分組ODMG提出的ODMG規范,目前已經是3.0版本,其中的OQL對象查詢語言相當具有吸引力。還有一些中立的機構提出了其它的一些標准化的對象訪問API,也可算是面向對象數據庫的規范之一。象前面提到的FastObjects和Ozone就是符合ODMG3.0規范的。
3 Java對象映射
話說回來,在一般的開發人員眼中,數據庫就是指關系數據庫,因此,很多應用還是采用簡單的JDBC來訪問數據庫。在開發的過程中,大家逐漸感覺到JDBC的局限性,比如調用復雜、容易產生資源洩漏等等,與面向對象的Java語言有一段距離,因此,很多開發小組開始思考如何將應用中的數據進行對象化建模,然後再想辦法與JDBC結合起來,這就是Java數據庫開發中的層出不窮的對象包裝技術。
3.1 對象包裝技術
3.1.1 傳統包裝與演變
傳統包裝顧名思義,就是最初出現的包裝方式,很多公司都經歷過這一步,產生了很多風格各異的包裝方法。當然,筆者也有過還算豐富的嘗試過程。
舉例來說,如果我們有一個用戶類:
public class User {
public int userId;
public String name;
public java.util.Date birthday;
}
我們可以將其當作一個簡單的數據類,然後寫一些工具方法來實現與JDBC的交互。這些方法,我們可以放到一個另外的工具類中,也可以放到User類中作為靜態方法。這些方法包括簡單的增、刪、改、查(以Oracle為例):
public class User {
public int userId;
public String name;
public java.util.Date birthday;
public static User addUser(String name, Date birthday) throws SQLException {
Connection conn = …; //獲取一個JDBC連接
PreparedStatement ps = conn.prepareStatement("…"); // 獲取一個序列值來作為用戶標識
ResultSet rs = ps.executeQuery();
rs.next();
User user = new User();
user.userId = rs.getInt(1); //讀取序列值為新用戶標識
user.name = name;
user.birthday = birthday;
ps = conn.prepareStatement("insert into …."); //插入用戶數據記錄的SQL
ps.setInt(1,user.id);
ps.setString(2,user.name);
ps.setDate(3,user.birthday);
ps.executeUpdate();
rs.close();
ps.close();
conn.close();
return user;
}
public static void deleteUser(int userId) throws SQLException {
Connection conn = ….;
//…
}
public static User getById(int userId) throws SQLException {
//…
}
//…
}
以上就是一個簡單的數據包裝的基本雛形,我們可以看到,這是一個非常簡單的JDBC包裝,一些代碼可以模塊化,以實現重用。另外,這段代碼還有很大隱患,就是中途如果出現異常的話,就會使系統出現JDBC資源漏洞,因為JDBC分配的資源(conn,ps,rs等)是不能被Java虛擬機的垃圾回收機制回收的。因此,我們的addUser方法就需要改成下面的樣子:
public static User addUser(String name, Date birthday) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
User user = new User();
try {
conn = …; //獲取一個JDBC連接
ps = conn.prepareStatement("…"); // 獲取一個序列值來作為用戶標識
rs = ps.executeQuery();
rs.next();
user.userId = rs.getInt(1); //讀取序列值為新用戶標識
user.name = name;
user.birthday = birthday;
ps = conn.prepareStatement("insert into …."); //插入用戶數據記錄的SQL
ps.setInt(1,user.id);
ps.setString(2,user.name);
ps.setDate(3,user.birthday);
ps.executeUpdate();
} finally {
//這裡注意一定要按照創建的順序關閉JDBC資源:
if(rs != null) try { rs.close(); } catch(SQLException ex) { ex.printStackTrace(); }
if(ps != null) try { ps.close(); } catch(SQLException ex) { ex.printStackTrace(); }
if(conn != null) try { conn.close(); } catch(SQLException ex) { ex.printStackTrace(); }
}
return user;
}
所有的數據庫訪問方法都必須進行這樣的包裝,當我們的數據類達到一定的數量後(比如十幾個,幾十個),這些方法占據了大量的代碼,維護困難、出現BUG機會增多,並且不易排錯,尤其是資源漏洞這種容易引起服務器穩定性問題的BUG。
為了保持數據類的純潔,我們可以將JDBC操作方法集中到一個公共工具類中去完成,這樣,這個工具類會非常龐大,但每個數據類會變得很簡單,這種方式,可以稱作是DataAccessObject模式,相當於EJB中的ValueObject,是脫離數據庫細節的純對象模型。
這些都是最基本的存儲處理,在基本增刪改查(這裡的查指按關鍵字查找對象)的基礎上,我們還需要進行復雜的匹配查詢(SQL),這使得我們的存儲處理代碼進一步復雜化。簡單地,我們可以寫一個類似的方法:
public static Collection findBy(String sql) throws SQLException {
//… (這裡獲取JDBC連接)
Collection col = new Vector();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while(rs.next()) {
User user = new User();
user.userId = rs.getInt(1);
user.name = rs.getString(2);
user.birthday = rs.getDate(3);
col.add(user);
}
return col;
//… (同前,這裡是清理JDBC資源的代碼)
}
這就是一個查詢接口的基本定義,查詢采用的語言仍是SQL。
如果我們需要將參數從SQL串中獨立出來以節省數據庫的解析時間並規范化,我們還需要將查詢條件作為參數傳遞到這個方法中去,方法的接口改為:
public static Collection findBy(String sql, Object[] params) throws SQLException {
//…
ps = conn.prepareStatement(sql);
for(int i = 0; i < params.length; i++) ps.setObject(i+1,params[i]);
//…
}
調用的時候sql參數中會包含一些"?"號,如:
elect ID,NAME,BIRTHDAY from USER where … = ? and … > ? and …
當然,也有一些開發團隊喜歡將所有可能的查詢都寫死成一個個的專用查詢方法,在其中完成對應的SQL操作,這一點類似於EJBQL,只不過是將EJBQL中容器實現的功能通過手工編碼來實現。這樣做使得查詢受到限制,但可以提供更保險的接口。
還有一些開發人員看到每個類中寫這樣一些查詢方法使得這個類的代碼變得龐大,維護麻煩,便將所有的查詢方法放到一個公共的工具類中去,只是在方法中再加入一個參數:Class cls來表示需要查詢哪個對象,使得每個數據類變得緊湊一些。當然,這樣的結果是那個公共類變得異常龐大,誰維護誰倒霉,可以說是犧牲一人,幸福團隊。不過如果這個人心理素質不夠好、壓力承受能力不強的話,一些對數據類的改動可能會受到他的阻礙,這時候就是"一夫當關,萬夫莫開"。
現在,我們已經實現了基本對象的包裝,現在才能開始考慮更多的問題。首先,我們可能會從規范化的角度出發,給每一個屬性加上讀寫訪問器getter/setter,在前面的User類中,可能我們會將基本屬性部分寫為:
public class User {
private int userId;
private String name;
private Date birthday;
//以下是針對以上屬性的getter/setter,一般可以用IDE工具生成
public int getUserId() { return userId; }
public void setUserId(int value) { userId = value; }
public String getName() { return name; }
public void setName(String value) { name = value; }
public Date getBirthday() { return birthday; }
public void setBirthday(Date value) { birthday = value};
//…
}
這樣,一個比較規范的數據類包裝就算完成了。
另外,我們知道,面向對象概念中,一個屬性可以是另一個對象,也就是說,對象之間是存在著引用關系的,這種關系還分為一對一、一對多、多對多等幾種情況。從一個既定對象出發,其某個屬性可以是另一個對象,也可以是包含另一組對象的集合。那麼,在我們的數據包裝裡面,當然最好也能方便地處理對象之間的關系。假定現在我們又有一個數據類Group:
public class Group {
public int grouId;
public String groupName;
public Set users; //set of User
}
這裡為了簡單表明含義,暫不采用getter/setter。
而User對所屬的Group有一個引用:
public Group belongTo;
在這裡,User.belongTo和Group.users就是一個一對多的關系。
在我們的數據類中,如何才能實現數據庫的存取呢?就算是不考慮Group.users這個反向關系,光是User.belongTo就是一件頭疼的事。如果我們在取出一個User對象時同時將其Group對象也取出來,可以保證不會在訪問某個用戶的組時得到一個null。不過這樣有幾個缺點:
1. 數據類的存取處理(JDBC)變得復雜,需要執行很多SQL讀取才行,有時候只需要訪問User的基本屬性時浪費時間和資源;尤其是對集合型屬性的預讀取,更加可怕
2. 如果按這個邏輯,雙向的關系處理變得危險,很容易陷入死循環,如果要避免,必須在類代碼中加入一些特別的機制,也是很麻煩的事
3. 如果對象之間的關系是更復雜的情況下,比如三個、四個對象之間互相關聯,那就是一場噩夢,對代碼的編寫和維護都異常艱難
於是,很多開發人員自然而然地退後一步,在User類中只保留一個groupId,並不保存Group對象,這樣,可以將User.belongTo屬性變成int類型,而另外寫一個getter方法:
public Group getBelongTo() {
return Group.findById(belongTo);
}
而在Group類中,干脆將users屬性去掉,只保留一個方法:
public Set getUsers() {
return new HashSet(User.findBy("select … from USER where BELONG_TO=?",new Object[]{ new Integer(groupId) }));
}
也許細心一點的讀者已經看出來了,這裡的幾個方法都沒有將SQLException捕捉,也沒有在方法中聲明,也就是說是有語法錯誤的,因為SQLException不是一個RuntimeException。不錯,確實是這樣,不過我們這裡為了簡單明了起見,省掉這些處理,以更直接清楚地表達意思。
這樣,實際上,我們的對象關系包裝已經名存實亡,在類的內部有很多用於訪問所引用對象的復雜代碼,這些已經違背了我們將對象關系保持的初衷。有些開發人員甚至不在類中保留訪問關系對象的方法,而是在客戶調用時再去訪問另一對象類的讀取方法,如:
…
User user = User.getById(…);
//對user對象進行一些訪問,如顯示其姓名等等
Group group = Group.findById(user.belongTo);
//對group對象進行一些訪問,如顯示組名等等
…
在這樣的代碼裡,實際上我們已經從根本上退回了關系數據庫的出發點,這與直接訪問數據表有多大區別呢?只不過是在表上面套了一層貌似面向對象的皮而已。不幸的是,這種方式還存在於很多應用之中。
從前面提到的這些面向對象包裝的細節問題,我們可以看到這種傳統包裝方式的一些主要的缺陷:
3.1.1.1 數據庫命名與對象設計命名的一致性問題
很多時候,我們興致勃勃地設計好一個類圖,並且經過評審之後,開始對它進行數據庫包裝,這時候,發現有些屬性名在具體的數據庫中與該數據庫的關鍵字沖突,不能用作表名或字段名,必須在數據表設計時采用另外的名稱,或者很麻煩地加上引號來使用。於是,寫數據庫處理代碼的人有意見了,他必須很清楚地記得一個屬性在類中是什麼屬性名,在表中又是什麼字段名,在編寫的類逐漸增多後,尤其是一個類經過歷次變動之後,或者經過很長時間又需要改動,並去處理這些JDBC代碼時,代碼維護人員簡單要發瘋了!
3.1.1.2 對象的查詢仍局限於SQL
這一點也是這種簡單的包裝方法最不能擺脫關系數據庫的地方。上面也已經說過,一些命名沖突帶來了很多維護工作量,代碼中必須還保留數據表的命名體系,而不是對象類的命名體系。這將使調用人員仍需要清楚記得兩套命名體系,無論時間經過多久,或者開發人員是否有流動。
此外,對於普遍需要用到的連表查詢,也給應用開發帶來困難,這些地方仍不能突破SQL的限制。
3.1.1.3 SQL資源占用多
在處理對象集合訪問或者處理集合類型屬性時,往往我們只能在同一個Connection中處理一切事務,這就要求一次性地將集合中的對象全部讀入,如果集合很大的話,容易造成數據庫擁塞,使得性能大打折扣,適得其反。這方面的優化也是很多開發人員一直在努力改進的。
3.1.1.4 對象關系處理復雜
前面提過,對象之間的關系處理上,普通的包裝技術是一種表面上的處理,在訪問時調用者仍需要用大量的代碼進行處理,並且,這還只是讀取,在寫入關系屬性時會有更多的細節問題需要處理。
3.1.1.5 面向對象特色只能應用一小部分
面向對象的包裝到前面所說的為止,都還只是很基本的處理,而面向對象的精華:繼承和多態,在這裡得不到任何幫助。我們放棄了很多合理的設計來遷就數據庫的包裝。
以上就是基本數據包裝的主要缺陷,還有更多的小問題也需要額外的處理。
不過,有責任心的開發人員當然不會就此善罷甘休,他們冥思苦想,可能會用更好的方式來實現對象之間關系的處理,而且還會加入一些延遲訪問的機制,將對象之間的引用在需要用的時候才去連接數據庫取數據,另外,在類的繼承上,他們也試圖去在數據庫包裝上得到體現。
值得感謝的是,一些富有經驗的團隊在經歷若干改進之後,毫不吝惜將他們的成果貢獻出來,並將其獨立化,成為可復用的組件。這也就是後來出現的對象包裝產品,包括商用的和免費的。
3.1.2 產品化包裝中間件
面向對象的包裝產品化後,逐步推廣開來,稱作關系/對象映射:O/R Mapping。並且,在產品化的過程中,產品提供者在產品中逐漸提供了更多的功能。先是一些自動命名轉換器得以應用,將客戶代碼中的類名、屬性名在內部處理的時候自動地轉換到對應的數據庫結構命名空間上,這樣,應用開發的時候不再關心具體的數據庫結構,不再需要記憶不同地命名體系,只需要一心一意地根據類圖來進行開發。在此之後,這些O/R Mapping產品的開發團隊又向前邁了一大步:引入了詞法分析器,提供面向對象的查詢語言!很多對象關系的查詢使應用開發變得非常方便,使O/R Mapping產品上了一個新的台階。
3.1.2.1 TopLink
TopLink是一個非常早期的產品,最初面向C++,後來也實現了Java的映射。TopLink性能優異,功能強大,並且提供了獨特的查詢過濾器機制,以及對關系的處理和查詢都非常有效,於是,TopLink逐漸從商用化O/R Mapping產品中勝出,成為市場上的最出色的映射產品。也正因為這一點,最大的關系數據庫廠商Oracle將其收購,成為提供最強數據庫和最強對象映射中間件的廠商。
3.1.2.2 Castor、Hibernate
TopLink雖然強大,但太強大的東西免不了得意忘形,TopLink開始將用戶鎖死到自己的產品上,查詢方式是最突出的。它的查詢體系含有很多別扭的概念(在我看來是如此),但為達到一般O/R產品不能達到的功能,開發者只能接受這些。慢慢地,也產生積怨,再加上其高昂的價格,讓很多新老用戶望而卻步。於是,免費的產品開始崛起。
免費的O/R Mapping工具有很多種,這裡只提其中最有影響力的兩種:Castor和Hibernate。
Castor是Exolab組織開發的面向Java的包裝工具,它最大的特色就是實現了大部分的ODMG OQL規范,在查詢上,可以象完全使用一個對象數據庫一樣類圖進行查詢(後面會有介紹)。它的原理是通過Java反射API去實現屬性的設置和讀取。不過由於各種原因,Castor後來的版本更新越來越慢,最終停步在1.0之前,成為至今未出到1.0正式版的O/R Mapping產品。不管怎麼樣,它還是一個相當不錯的產品。
Hibernate是一個現在很火熱的O/R Mapping產品,目前已經出到2.0版,它功能一樣強大,同樣使用Java反射API進行對象的設置,但它的查詢語言就是一套比較獨特的體系,這一點有點類似TopLink,但Hibernate更具有親和力,對關系的查詢更方便,只不過比起Castor來,在方便性和規范性上還是稍遜一籌。就目前狀況而言,Hibernate的用戶量和技術支持要強一些。
3.2 面向對象的數據庫查詢
在對數據庫進行面向對象研究的過程中,軟件世界的開發人員和設計人員們發現:對數據庫能夠進行對象化的查詢,才是對數據庫進行徹底的面向對象化。這體現在我們使用一種全新的數據庫查詢語言,能夠很簡潔易懂地對數據庫中的對象進行查詢。一個典型的例子如下:
假設我們已經有前面提到的兩個數據類:User和Group,它們之間有一對多的關系:User.belongTo和Group.users。在數據庫中已經存在很多這兩個類的實例,以及相互之間的關系。我們可以使用下面的對象式查詢語言來查詢符合條件的User對象:
select * from User where User.belongTo.name=\'GROUP1\'
或者
select userId,name from User where User.belongTo.name=\'GROUP2\'
等等。從中我們可以看出,通過使用面向對象中的成員屬性指定符".",可以讓我們達到SQL中的連表的效果,實際上,第一個句查詢的SQL等價版本是:
select a.* from USER a, GROUP b
where a.BELONG_TO = b.GROUP_ID
and b.NAME = \'GROUP1\'
由此可見,對象式的查詢語言,比起實現同樣功能的SQL語言來說,簡單了很多,意義也更明確,更符合使用者的思維習慣。在類圖比較復雜、查詢涉及的類又比較多的時候,這種新型的查詢語言體現出絕對的優勢。
3.2.1 ODMG,OQL,Java Binding
在面向對象式查詢語言的研究過程中,開發人員們逐漸實現了相似的查詢語言,然後互想取長補短,最終在ODMG組織(www.odmg.org)的統一下,形成了規范化的語言:ODMG OQL,這是一種完全面向對象的數據庫查詢語言,語法與前面提到的類似,不過考慮了更廣泛的情況,語句更加簡潔而嚴謹。
OQL並不是針對某種語言的,它可以被應用到很多種開發語言中,它不象SQL那樣只是純字符串式的查詢語句,因為面向對象查詢中還必須提供相關類的信息,所以OQL需要在編程語言中實現一些特定的API。
目前,ODMG的OQL已經被規范化地應用到SmallTalk、Java、C++這些面向對象的程序設計語言當中,在ODMG的規范中,這幾個模塊被稱作SmallTalk Binding、Java Binding和C++ Binding。
不過,由於歷史原因,ODMG並沒有象想象中地那樣得到廣泛應用,現有的十幾個面向對象數據庫中,采用ODMG OQL規范的少之又少,目前也只有FastObjeccts、Ozone這些產品采納了這個規范,而象Versant這樣的大廠商還沒有采取OQL來查詢數據庫,而是自己定義了自己的一套API,稱作VQL(Versant Query Lanaguage)。
在JDO之前的O/R Mapping產品中,也有一些產品使用OQL(有時候是其子集),比如Castor、Apache的Jakarta小組開發的OJB等等。
3.2.2 第三方協議
軟件世界是一個多姿多彩的世界,總有那麼一些好事之士不斷地冒出新奇的想法。還有一些開發面向對象數據庫的組織制定了自己的一套對象式數據庫查詢語言,自己的規范。
不過這些規范相對來說,影響力小得多。比起ODMG來,可以說應用范圍太小,更不用說與SQL這樣廣泛應用的標准進行比較了。
3.2.3 EJBQL
Sun為了使Java應用在企業級數據庫應用中,不遺余力地推廣J2EE,在2001年的時候,推出了EJB2.0規范,其中包含了富有特色的面向CMP方式的EntityBean的查詢語言:EJBQL,功能類似於ODMG OQL,只不過只能在EJB發布時靜態地存在於應用描述符中,不能在程序中動態地使用。這是EJBQL最大的弱點,也許EJB3.0規范會將其動態化,但到了那一天,世界多半已經不是現在的樣子了。
3.2.4 JDO
JDO中有最近規定的一個對象式查詢語言規范,稱作JDOQL,比起OQL來,JDOQL將查詢語言中的很多元素與Java語言緊密地結合在一起,有的人覺得麻煩,有些人覺得規范,評論各不相同。從筆者個人的角度來看,這樣有利於沒寫過數據庫應用、沒用過SQL的新手很快地習慣JDOQL,但實際上,有多少人會在沒寫過SQL,沒了解過關系數據庫的情況下去用JDO寫數據庫應用呢?畢竟市場說明了一切。個人認為,JDO中對數據庫對象的查詢多少顯得有些累贅,如果能更簡化一點,那將更吸引使用傳統SQL的開發人員。
4 JDO歷程與主要產品
說起JDO,其來由還有一段特殊的背景。Java語言在JDK1.1達到比較實用的目的後,企業級數據庫應用也正是軟件開發市場中的重要組成部分,Sun看到這一點後,也希望通過Java這個強大的武器在數據庫開發市場攻占市場份額。JDK1.2推出後,Sun同時推出了面向企業應用的EJB,對基於java的中間件服務器框架進行了規范化定義,這就是J2EE。不過在JDK1.2時,Java的速度還是不能與傳統的C/C++和Delphi這樣一些應用開發語言相比。為了防止業界對Java的激情因此而消退,Sun宣布將在JDK中加入強大的虛擬機技術HotSpot,其中包含更先進的垃圾收集算法和更優化的Java字節代碼再編譯技術(相當於JIT,Java即時編譯技術)。HotSpot引起了Java關注者的極大興趣,但Sun的HotSpot一拖再拖,最後包含在JDK1.3中出來時,性能也沒有象預期的那樣好,比起C++編譯生成的代碼來還是有一段距離。
這個時候,大家開始對Sun心存懷疑,而Sun深知這一點,於是將公眾的注意力趕緊轉移到EJB上,從EJB1.0到EJB1.1,再到EJB2.0,業界又開始關注J2EE中間件技術上來。很快,開發人同發現用EJB來編寫數據庫應用還是有很大的難度,雖然在分布式技術上EJB確實有很大的價值。在這個時候,Sun決定推出JDO技術作為輕量級的Java數據庫訪問規范,而這一點也受到很多傳統O/R Mapping市場的歡迎。為了與傳統O/R Mapping有所區別,Sun一開始就高姿態地將JDO定位成不只是面向關系數據庫的Java規范,而是針對所有的存儲技術,包括面向對象數據庫和其它類型的存儲體系(如文件),就象EJB EntityBean一樣,雖然。就筆者的角度,這個做法使第一版的JDO拋棄了很多傳統O/R Mapping提供的面向關系數據庫的功能,可以算是一個失策。
4.1 規范提出、JSR
JDO最早是由Sun召集眾多的O/R Mapping開發團隊集中起來共同提出的,首先是通過會議確定了JDO需要包括的內容,然後正式提出一個Java規范請求(JSR-12),正式開始了JDO規范的制定。下面是主要的進展裡程碑。
JSR #000012 approved in July 1999
1999-8組建的專家小組:包括Sun、Apple、BEA、IBM、Oracle、SAP、WebGain等
2000-5 完成公開評論草案
2000-6 在JavaOne上引入
2001-3 最終草案0.93
2001-5 最終草案0.96公布
2001-6 在JavaOne上啟動
2001-11 最終草案0.98
2002-4 1.0版正式公布
2002-8 1.0.1修正版
2003-8 2.0規范啟動
…
4.2 Oracle與JDO
作為JDO專家組的重要成員,同時作為最大的關系數據庫廠商的Oracle地位顯然非同一般。在JDO規范中,Oracle也可說是立下汗馬功勞,很多API的形成,Oracle都提供了很重要的參考意見,最終的投票Oracle也是毫不猶豫。
可是,世間的事總是變化莫測的,就在JDO1.0快出台之時,Oracle收購了TopLink,這一點使Oracle的身份變得特殊而復雜。TopLink是一個商業產品,是以商業利益為目標的,而Oracle也是追求利益最大化的典型商家,這一點與JDO的開放精神背道而馳。因此,我們看到後期Oracle對JDO的不積極態度,甚至在前一陣的JavaOne大會上有人從Oracle的角度非正式地攻擊JDO。
4.3 主要產品以及各自特點
在JDO規范制定的同時,出現了幾個主要的JDO產品,有美國的基於對象數據庫的FastObjects j1、法國的支持Versant對象數據庫、文件數據庫、主流RDBMS的LiDO、南非的JDOGenie、德國的JRelay等等,這些都是很不錯的JDO產品。下面列舉一下我對主要的幾個產品的印象:
LiDO(法國LibeLis公司)
我對JDO的認識主要是通過LiDO這個產品,它在2002年3月的一份圖文並茂的教程中簡要解說了JDO的使用和優點。這個教程可以在這裡下載:http://www.objectweb.org/conference/JDO.pdf。LiDO的特色是大而全,支持文件型數據庫、RDBMS、ODBMS,甚至是XML數據庫。不過配置較麻煩。最新版本是2.0RC。
KodoJDO(美國SolarMetrics公司)
Kodo是JDO的中流砥柱之一,在JDO1.0還未最後通過的時候,它就是一個比較成熟的產品了,其特點是注重性能和穩定性,目前最新版本是2.5.0,是客戶最多的產品。
JDOGenie(南非HemSphere公司)
這是目前我最推薦的產品,最新版本是1.4.7,性能也不錯,穩定性還有待驗證,但它有一個最大的特點:集成性好,最易學,其公司的CTO David Tinker也是一個善解人意的年輕人,采納了很多網友的意見對產品進行改進,主要是在配置上非常方便,有一個專門的圖形界面工具,可以進行配置、數據庫生成、對象查詢等等很實用的功能。
JRelay(德國ObjectIndustries公司)
這也是一個出現得比較早的產品,也有一個GUI工具用於配置,曾幾何時,這個工具還是相對很方便的,但一年多過去了,好象沒什麼進展,最新版本是2.0,我試過一段時間,後來就沒有再跟進了。
FrontierSuite for JDO (美國ObjectFrontier)
這個產品與JRelay、Kodo一起,可算是早期的JDO三個主要產品,它支持正向開發和反向開發(Reverse Engineer)。它的特色是反向工程(從表結構生成數據類)比較方便,與UML的結合也很強,不過真正運行起來的時候,配置復雜。
TJDO(一群跨國界的有志之士)
這是一個在Sun提供的參考產品(Reference Implementation)的基礎上加入一些擴展功能而形成的一個免費產品,目前最新版本是2.0beta3,不過進展也緩慢,這個版本已經出現好幾個月了沒有進一步的更新。
5 目前狀況與未來展望
從2002年4月JDO1.0規范正式公布以來,各個產品層出不窮,從商業到免費的層次,都有不錯的產品推出。象Kodo、Lido、JDOGenie等產品都已經比較成熟,可以考慮投入開發使用。在2002年8月,JDO又推出1.0.1修正版,修正了1.0版規范中的一些文字錯誤,以及輕微地改進了部分異常定義,不過改動都不大。從現在的情形來看,除了Kodo有一些大學的項目用到外,好象還沒看到多少使用JDO作開發的應用。
JDO畢竟是一個新技術,從概念上到實際應用上對其掌握的用戶還不多,而這些產品在配置、使用上的方便性易用性還有待大幅度改進,因此,真正用JDO來開發項目的用戶還廖廖無幾,至少我還不知道有哪些項目使用了JDO。我自己也嘗試使用JDO來開發項目,但由於一些JDO1.0版本中還不夠完善的一些硬傷(比如不支持關系數據庫統計功能),使我對JDO仍處於觀望階段。
在八月中旬進行的JDO2.0規劃會議中,來自各國的各個JDO產品廠商、JDO技術咨詢公司、JDO研究機構的代表匯聚一堂,將各自收集到的用戶對JDO2.0的需求總結起來,提出一個個的新的議題,並且確定了每個議題的JDO規范撰寫負責人,比如高級fetchGroup特性由目前實現得最好的JDOGenie的CTO David Tinker負責,關於managed-relationship特性由Kodo的產品總監負責,用戶要求最多的JDO對象與PersistenceManager的脫鉤/重掛鉤特性由Sun的Craig Russell親自操刀,等等。
最具有吸引力的JDO2.0議題,筆者個人認為是專門為關系數據庫設立的子規范JDO/R,這也是我一直以來最關心的,這將使目前JDBC的開發將可以被JDO完全取代,並且保證開發過程保持面向對象的特色。還有一些將一個類映射到多個表之類的特性也在規范化的列表上,這將有利於DBA在不影響應用開發的前提下根據需要更改數據表結構,實現更好的性能。類似的新特性還有很多,粗略地看,這些都規范化起來後,真不知道各個廠商還能做什麼樣的擴展特性,也許以後廠商之間拼的只能是技術支持服務和產品性能了,當然,最後還有價格的競爭。
說了這麼多,我想大家關心的還是JDO2.0到底什麼時候能出來,我們什麼時候可以用上它,什麼時候有中文版產品,價格到底如何。這些問題目前筆者還無法一一回答,只能根據筆者所掌握的信息初步解釋一下。據前幾天的JDO2.0啟動大會上David Jordan的預期,JDO2.0正式版將在18個月後正式完成。那正式完成後廠商什麼時候才能提供符合規范的產品呢?這個問題不用擔心,因為規范在制定的過程中會不斷地公布公眾預覽版(Public Review),這樣,廠商可以先實現其中的功能,等規范正式完成後,也不會有太大的變化,廠商也不會需要太多時間來跟進最終規范。所以,我估計一年之後,我們就可以在JDO2.0上進行開發了。至於價格如何,1.0規范的產品初步印象是每個開發人員需要一個License,一個License大概2000美元。如果JDO2.0產品的價格還保持這麼貴的話(至少對中國用戶來說),我們也還有很多免費(如JPOX,TJDO)或半免費(如JCredo)產品可以選擇。最後一個關於中文版的問題,有待於廠商對中國市場的考察,或者看看是否有國內的JDO產品了。不過,在JDO2.0規范制定的同時,我們可以先集眾人之力將其普及,使廣大的使用Java語言進行數據庫開發的開發人員都來認識JDO,了解JDO,甚至將其用到項目開發之中。
也許,JDO的目標已經吸引了Java世界以外的人,微軟發現了這一點,也立即計劃在.NET體系中加入一個仿照JDO的中間件,具體是采用ObjectStore的產品,ObjectStore是一個同時做JDO和.NET-DO(姑且使用這個名稱)的公司。
在這裡,筆者可以大膽地預測,在未來的一兩年內,JDO將在Java世界大放光彩!
5.1 一點花絮
在2003年6月舉行的JavaOne大會上,JDO備受矚目,很多開發者或開發組織對其產生了極大的興趣,在各大網站媒體上也頻頻曝光,大多對其評價不錯,當然,也有一些負面的評價。
不過,網站上的報道也不可盡信,比如服務器領域的權威網站TheServerSide.com上介紹JavaOne大會第二天關於JDO的一次會議的報道就有誤導之嫌,從報道的內容來看,好象會議用來答疑與交流的的最後五分鐘全被一個好事者占據,這個人拼命鼓吹Oracle的TopLink解決方案和一些易用性的改進,從而攻擊JDO的市場前景。後來,我就這一信息與參加會議的JDO專家組的David Jordan交流時,他糾正了我的錯誤理解,實際情形是:那個好事者是一個積極的JDO擁護者,他花了超過五分鐘時間向大家揭露Oracle的TopLink改進實際上也是抄襲JDO的概念而進行的一些API改動!不過有一點是相同的,這個好事者占據了大家的提問時間,使這次會議在意猶未盡中結束。