用JDOGenie進行基於JDO的項目開發嚮導

如何用JDO開發數據庫應用

(本文的版權屬作者本人,歡迎轉載,但必須註明出處和原作者)

本文將介紹如何使用Sun公司的最新《Java Data Objects 》規範來進行基於數據庫的簡單應用程序的開發,從而使讀者對JDO有一個直接的感性的認識,爲更深入的開發作鋪墊,同時也希望拋磚引玉,讓更多的富有經驗的高手也參與到推廣JDO的進程中來,爲讀者提供更多更精彩的文章!

1. JDO是何方神聖,難道是ADO的翻版?

本節對稍熟悉JDO一點的讀者來說,可能算是老生常談,一堆垃圾,不如回收掉算了。不過我卻認爲這些是實話實說,有感而發,不吐不快,對新手可能也有一定的幫助,至少應該有一點共鳴吧。所以,老手請直接跳過本節。

1.1. Java的優點

自從Java語言面世以來,它那幾乎完全面向對象的特性和解放我們程序員的自動垃圾回收機制給我們展現了一個全新的開發天地:原來程序還可以這樣寫!我用過幾年C++,裏面的指針簡直折壽!我還記得有些功能裏面不得不使用類似“***lpszInfoMapOfMap”之類的變量,它是指針的指針的指針,要在編碼過程中準確地把握這一點已屬不易,何況還要記得釋放每一處佔用的內存,並且還不能釋放多次(嚴格地說,應該是將自己申請的內存進行且只進行一次釋放)!我至今都還很佩服當年清晰的頭腦,然而在調試過程層出不窮的“AccessViolation”和“NullPointer”錯誤竟使我一夜白頭!(有一次熬夜調試一個問題,第二天憔悴了很多。)C++之後,我也用過三年以上的Delphi,程序代碼好理解、易維護了很多,不過指針仍是胸中永遠的痛!直到Java,才使我脫離苦海,進入“按思維的速度進行開發”的時代……

當Java的速度得到很大改善後,我們開始用它來寫數據庫應用,但說實話,Java的數據庫方面還很原始,圖形界面編程中的數據庫組件很不好用,再加上主要寫的是Web應用,只有JDBC接口可供選擇。提起JDBC,我相信很多讀者都會有這樣的印象:概念太多,嚴密但麻煩,尤其是資源的釋放也是一大問題。比起微軟的ADO來,簡直是一團亂麻,容錯性尤其差勁。

1.2. 對象包裝技術,百家爭鳴?羣魔亂舞?

於是,從規範化開發的原則出發,我們開始寫自己的JavaBean來包裝數據對象,使數據對象化,避免太多的地方涉及JDBC操作。但一些問題也隨之而來:靈活性不夠,接口死板,性能低下,這使我一陣苦惱。於是,“君子性非異也,善假於物也”,咱也上網去找點“技術支持”!很快,竟然被我發現了“Castor JDO”,一個專用於數據包裝的撞闋榧峁┝薕DMG標準的OQL作爲查詢語言,方便且容易理解,比SQL好多了。這讓我享受了一段時間的“面向對象的數據庫開發”的好處,一句話,“效果不錯,還實惠!”。

然而,好景不長,Castor一些內在的BUG影響了穩定性,而這個免費產品的更新又太慢,一直未能解決。只好放棄。“執手相看淚眼,竟無語凝噎”!怎麼辦?要知道,由儉入奢易,由奢入儉難,吃過肉的人,怎能忍受只能吃菜的生活!象《甲方乙方》裏面那個一心想吃素的大款還是不多見的。對我們來說,再使用JDBC原始調用似乎難以下嚥,再用JavaBean包裝又有點返古,於是我又開始了網上的搜尋歷程。餘秋雨先生有《文化苦旅》,咱這也算是《編程苦旅》了,呵呵,苦笑。

從網上的資料來看,我的這些經歷也是很多Java開發同仁的共同經歷,無論是國內還是國外,不過從實際情況來看,國外的研究更深入更廣泛一些,至少從網上所能找到的資料來說是這樣。美國從八十年代起就開始研究面向對象的數據庫ODBMS,目前已有一些成形的產品,比如Versant公司的Versant數據庫,FastObjects公司的FastObjects t7數據庫,以及其它一些相對市場份額小一些的諸如ObjectStore等公司的產品,當然,也不乏一些免費的產品,如Orient等等。總的來說,ODBMS儘管擁有面向對象的優點,但由於歷史原因,在與關係數據庫RDBMS的競爭中始終處於下風,基於RDBMS的應用還是佔絕大多數,因此,出現了Object-Relational映射的一些工具,前面提到的Castor就是近年來出現的一個工具,實際上更早的時候,已經有一些成熟、穩定的商業化產品出現,比如前一陣被Oracle收購的TopLink,被BEA收購的WebGain等等,比較有名氣的CocoBase等等。

象TopLink這樣的產品我也瞭解了一下,功能確實強大,性能、穩定性都有優勢,然而,其同樣強大的價格和古怪的API令我卻步。我很擔心被鎖定在某個產品上面,無法脫身,衆所周知,Java給我們的就是一種自由的感覺,自由,永遠是那麼地吸引人。

出路在哪裏?JDO浮現在我眼前。

1.3. JDO浮出水面,可別以爲是ADO

JDO自1999年起就由一些經常寫數據庫對象映射層的富有經驗的開發人員提出大綱,他們在長期的面向對象開發中進行了大量的數據庫方面的處理和對象化包裝,終於,多種多樣的包裝方式引起很多兼容性方面的問題。於是,一些主要的開發團隊就聯合起來,以SUN爲領頭羊,制定了JDO規範。它的目標不是取代JDBC或EJB,而是在JDBC的基礎上進行包裝,同時又可以做EJB的底層(CMP),簡化J2EE服務器提供商的工作。JDO主要面向中小型規模的項目,不過隨着產品提供商(Vendors)給出越來越多的功能(Feature),比如分佈式的同步控制等等,JDO的作用也越來越大。
JDO規範在Sun的富有經驗的Craig Russel的帶領下,經過三年的討論,終於在2002年四月形成了第一版。目前最新版是1.0.1版,在 http://access1.sun.com/jdo/ 可以看到。
(大家應該知道,Java的規範形成時間一般都比較長,因爲它太開放了,任何人都可以發表意見,參與制定規範的人都要考慮這些意見)

ADO是微軟的數據訪問組件集合,相信很多寫過基於ASP頁面的數據庫應用的朋友都印象深刻,快速、容錯性強是它的特點,不過擴展性就不敢恭維,寫多少年都是那幾個東東,無法超越。一些剛接觸JDO的讀者可能會將JDO與ADO搞混起來,以爲是Java版的ADO,那就大錯特錯了,兩者風馬牛不相及,可以說不是一個檔次上的東西。慶幸的是,微軟沒有將“xDO”這類縮寫註冊成商標或專利,否則兩個大塊頭又要打官司了,嘿嘿,我倒是喜歡看熱鬧,打起來同樣精彩!

1.4. JDO產品介紹

JDO規範自從2002年4月推出以來,出現了很多種各有特色的產品,當然,這些產品都遵循JDO規範,不會影響你寫的JDO應用的可移植性。下面列舉一下我對各個產品的印象:(參見第6節的參考文章:《JDO資源介紹》)

    1. 教父:LiDO(法國LibeLis公司)
      我對JDO的認識主要是通過LiDO這個產品,它在2002年3月的一份圖文並茂的教程中簡要解說了JDO的使用和優點。這個教程可以在這裏下載:http://www.objectweb.org/conference/JDO.pdf。LiDO的特色是大而全,支持文件型數據庫、RDBMS、ODBMS,甚至是XML數據庫。不過配置較麻煩。最新版本是2.0RC。
    2. 霸主:KodoJDO(美國SolarMetrics公司)
      Kodo是JDO的中流砥柱之一,在JDO1.0還未最後通過的時候,它就是一個比較成熟的產品了,其特點是注重性能和穩定性,目前最新版本是2.5.0,是客戶最多的產品。
    3. 最佳傳教士:JDOGenie(南非HemSphere公司)
      這是目前我最推薦的產品,最新版本是1.4.7,性能也不錯,穩定性還有待驗證,但它有一個最大的特點:集成性好,最易學,其公司的CTO David Tinker也是一個善解人意的年輕人,採納了很多網友的意見對產品進行改進,主要是在配置上非常方便,有一個專門的圖形界面工具,可以進行配置、數據庫生成、對象查詢等等很實用的功能。強烈推薦!
    4. 大家閏秀:JRelay(德國ObjectIndustries公司)
      這也是一個出現得比較早的產品,也有一個GUI工具用於配置,曾幾何時,這個工具還是相對很方便的,但一年多過去了,好象沒什麼進展,最新版本是2.0,我試過一段時間,後來就沒有再跟進了。
    5. 兩面派:FrontierSuite for JDO (美國ObjectFrontier)
      這個產品與JRelay、Kodo一起,可算是早期的JDO三劍客,稱它爲兩面派是因爲它正向開發和反向開發都還可以。它的特色是反向工程(從表結構生成數據類)比較方便,與UML的結合也很強,不過真正運行起來的時候,配置複雜。當初該公司曾許諾我以10%的價格買一份,可惜我當時沒在意。如果上天再給我一次機會……
    6. 免費午餐:TJDO(一羣跨國界的有志之士)
      這是一個在Sun提供的參考產品(Reference Implementation)的基礎上加入一些擴展功能而形成的一個免費產品,目前最新版本是2.0beta3,不過進展也緩慢,這個版本已經出現好幾個月了沒有進一步的更新。

 

以上這些是我用過的比較有代表性的產品,還有很多商業產品,以及其它一些或規範或不完全規範的免費JDO產品(如XORM、OJB等),這裏不再一一列舉,有興趣的讀者可以到 http://www.jdocentral.com/ 去進一步瞭解。

2. JDOGenie是哪路英雄

前面已經提過,JDOGenie是南非的一個商業化產品,可別小看南非人,他們的收入可不少!當然技術也不錯。如果讀者做過UML的建模和設計,可能會知道Together Control Center這個產品,也就是最近被Borland收購的一個UML設計工具。JDOGenie的出品公司HemSphere就是Together的南非總代理和合作伙伴。

說到這裏,插句題外話,IBM收購了Rational,Borland收購了Together,Sun收購了DescribeUML,一場IDE+UML的大戰又將上演。

好,書歸正傳,JDOGenie是我目前最推薦的產品,原因是易學易用,簡單上手,對於想學習JDO的朋友是最適合不過的了!它有一個圖形界面的配置工具,可在裏面進行數據表映射、SQL操作、JDOQL查詢等等功能,非常方便。對採用JDOGenie的Web服務器也可以通過這個圖形工具進行監控,可以瞭解哪些查詢費時,哪些查詢執行次數多等等,有助於數據庫優化調節。

俗話說,百聞不如一見,下面先給幾張圖片,過把癮先:

CSDN_Dev_Image_2003-7-21253150.jpg

控制檯界面(WorkBench):

CSDN_Dev_Image_2003-7-21253152.jpg

 

以下的內容是以JDOGenie爲底層來介紹JDO的開發流程的,所以需要先下載JDOGenie:

先到http://www.hemtech.co.za/jdo/download.html下載最新版本(本文使用的是1.4.7),然後點擊該頁面上的“Obtain Evaluation License”鏈接獲取一個月的試用License(一個月會不會太少了?放心,該公司在快到期時會發佈一個新的License的)。在獲取試用License的時候需要填寫一些資料。

3. 我們要做什麼--需求描述

本節主要描述本文中將要做的應用程序的功能。這個應用程序非常簡單,是基於一位網友提出的一個《銀行信用卡交易系統》中提取出來的一個功能子集,並作了一定的簡化和功能改動,以便能體現JDO的特點,主要完成以下功能:

    1. 錄入信用卡資料。
      信用卡資料包括以下信息:
      • 卡號(自動生成)
      • 持卡人姓名
      • 身份證號
      • 地址
      • 聯繫電話
      • 開戶日期(自動生成)
      • 開戶金額
      • 目前餘額(自動計算)
      • 允許透支額
    2. 瀏覽信用卡信息
      瀏覽當前所有的信用卡資料
    3. 交易刷卡
      針對信用卡產生一次交易,需要錄入以下信息:
      卡號、交易額
      如果超出透支額,則提示無法完成交易。
      如果交易成功,系統自動記錄交易時間
    4. 存款
      錄入卡號和存款額
    5. 查詢透支報表
      查看當前所有透支的信用卡
    6. 查詢交易明細
      可根據持卡人身份證號查詢所有的交易信息

以上就是整個應用的功能,非常簡單,如果採用JDBC,我們立即會想到先建兩個表:信用卡表和交易表,二者通過卡號關聯,然後這些功能就是一堆SQL和這兩個表組合而成的大雜燴。然而,我們現在要採用JDO來做,怎麼做呢?請繼續往下看……

4. 開發過程

剛纔看上面這一段功能需求的功夫,JDOGenie也應該down下來了,如果還沒有的話,感緊安裝一個ADSL吧!(別亂猜,我可不是電信的職工!咱只有羨慕的份……)。

採用JDO進行開發的過程大致如下:

  1. 先編寫原始的數據對象模型代碼並編譯成爲.class文件,這些數據對象稱作原始對象(POJO,Plain Old Java Objects)
  2. 然後編寫存儲描述符Metadata,也稱元數據,表明一些與存儲相關的設置,比如哪些類需要保存到數據庫,每個類中的哪些字段需要優先讀入,哪些字段延遲讀入(LazyLoad)等等,這個metadata必須放到CLASSPATH中,擴展名是“.jdo”。
    metadata的編寫一般可以通過工具來完成,比如JDOGenie的工作臺(WorkBench)就可以輕鬆地完成。
  3. 寫完描述符後,就採用某個JDO產品的增強器(Enhancer)來根據元數據的描述改造編譯生成的.class文件,使這些類代碼JDO化,然後就可以在其它代碼中調用了。應用代碼中主要通過JDO存儲管理器(javax.jdo.PersistenceManager,以後簡稱PM)來完成對數據對象的增加、刪除、修改、查詢等操作。

示意圖如下(摘自Versant公司的JDO教程):其中的XML Config即是指*.jdo

CSDN_Dev_Image_2003-7-21253154.jpg

4.1. 如何建模

當我們將自己的頭腦OO化之後,以上的需求在我們頭腦裏變成了幾個基本的對象:信用卡和交易記錄,以及對這些對象進行的一些操作。(其實我們的頭腦本來就是基本客觀世界的對象的,應該說本來就是OO的,要不然大腦怎麼會是一些環成“O”狀的腸子呢?:D)

爲簡單明瞭,我們這裏的建模也不採用類似ROSE或Together這樣的大型的UML工具了。在Duke或Quake中,有時候步槍,甚至是電鋸,會成爲最有效最直接的殺人工具,而CS裏面手槍也往往出奇制勝。現在,在程序開發的戰場上,我們祭出最原始的利器:記事本!相信沒有一個人不會使用它。簡單點說,我們下面的內容都直接以源代碼作爲建模的說明。

首先,我們分析信用卡這個類,很簡單,將前面列出的字段作爲屬性加到類中即可,這和建表的過程其實差不了多少(只是碰到對象之間的關係時,思路會有不同)。

package credit.system;


import java.util.Date;


public class CreditCard {
    String name; //姓名
    String address; //地址
    String idcard; //身份證號
    String phone; //電話
    Date createTime; //開戶日期
    Date lastTransactionTime; //最近一次交易的日期
    float initialBalance; //開戶金額
    float balance; //目前餘額
    float allowOverDraft; //允許透支額

}

咦,好象有什麼地方不對勁?不錯,你的眼光真犀利!“我搞了這麼多年數據庫應用開發,從沒見過沒有關鍵字的表,也沒見過這樣一個沒有標識字段的類!卡號哪兒去了??!!”

是啊,卡號哪兒去了?沒有卡號的信用卡誰敢用?趁早捲鋪蓋回家吧!

別急,這裏先給大家介紹一下JDO的一個關於對象標識的概念:標識實際上只是一個對象的唯一標記,有點象一個對象的內存地址,對傳統的數據庫來說,就是一條記錄的主鍵。JDO認爲,如果關鍵字只是用於標記一個對象的唯一性,而不參與業務邏輯(比如計算),則不必將它放到類代碼中,這種唯一性的維護只需要由JDO中間件(Implementation)去完成,這種對象標識叫做Datastore Identity,一般實現上是使用遞增整數;如果標識也參與業務邏輯(如主鍵是創建時間,會用於排序或範圍查找),則可以在類代碼中出現,這種對象標識叫做Application Identity。關於這些概念,請參考本文尾部參考文章中的《JDO對開發的幫助有哪些》一文。

在上面的信用卡類中,我們認爲信用卡號只是對信用卡的一個標識,不參與業務邏輯,所以我們採用Datastore Identity的方式,讓標識的唯一性由JDO產品去維護,就象對象在內存中的地址不需要我們在程序代碼中指定,而是由JVM去維護一樣。

咦,好象又有什麼地方不對勁?不錯,你的眼光還是這麼犀利!“你的信用卡沒有標識,那我的交易記錄怎麼去關聯它??!!”

對啊,以前我寫的JavaBean包裝的數據對象,也需要有一個主鍵屬性,另一個對象通過一個同樣類型的屬性來與這個對象關聯,現在你這個主鍵屬性都沒了,我怎麼去關聯呢?無的放矢?

這個問題問得很好,也非常典型(注意,是非常典型,不是“非典型”)。

不過問這個問題的人,應該都是寫過多年數據庫應用的富有經驗的開發人員,數據表、主鍵、外鍵關聯的意識已經深入頭腦,就算變成Java類,主鍵外鍵還是陰魂不散。這種方式可謂“換湯不換藥”,沒什麼實質的變化,這樣的對象模型也不能體現出對象之間的關係,只能通過程序員自己去把握。說實話,我最初也是這樣去做對象包裝的,慚愧慚愧,現在讓我們步子再大一點,觀念再開放一點,看看JDO中的概念吧:對象之間如果有關係的話,只需要直接將關係到的對象聲明爲一個該類型的屬性(或屬性集合)即可。

這樣,我們的交易記錄類就寫成了下面的樣子:

package credit.system;

import java.util.Date;

public class TransactionRecord {
    Date createTime; //交易發生時間
    float amount; //交易金額
    String note; //備註

    CreditCard card; //信用卡
}

在這個類中,我們看到裏面沒有一個“信用卡號”的屬性,取而代之的是一個信用卡對象“card”,這樣,我們就不會需要在通過交易記錄取得相關信用卡的時候去調用一條查詢語句來取得信用卡對象了,只需要簡單地讀取這個交易記錄對象的card屬性即可得到。這也是面向對象的便捷性之一。

有了這兩個類,我們的《銀行信用卡交易系統》的基礎也就搭起來了。

4.2. 如果怕出亂子……建議的代碼規範

記得以前聽過一句話,“世間永恆不變的真理就是不存在永恆不變的真理”,這裏,我也想說一句:編程世界裏最完美的解決方案就是不要認爲有最完美的解決方案。(說什麼啊,簡單聽不懂。呵呵,我自己也有點聽不懂)。

我想說的是:JDO也是有一定的限制的,不能讓你完全地展開雙翅(注意,是“魚翅”),在面向對象的大海中遨遊。爲什麼JDO會有限制呢?因爲它的原理是將你的類代碼進行一定的改造,將JDO涉及的一些管理和維護代碼插入到類代碼中,這樣,你的調用代碼可能需要進行一些改變。這些就是JDO的限制。簡單地說,如果你得到了一個TransactionRecord類型的對象tr,想通過它取得涉及的信用卡對象,不建議通過下面的代碼:tr.card得到,而是建議將這個屬性聲明爲private的,然後給出一個getter來獲取(getCard()),也就是進行JavaBean式的屬性包裝。這樣,我們的兩個數據類就會變成下面的樣子:

CreditCard.java:


package credit.system;


import java.util.Date;


public class CreditCard {
    String name; //姓名
    String address; //地址
    String idcard; //身份證號
    String phone; //電話
    Date createTime; //開戶日期
    Date lastTransactionTime; //最近一次交易的時間
    float initialBalance; //開戶金額
    float balance; //目前餘額
    float allowOverDraft; //允許透支額
	
    public String toString() {
return "信用卡:餘額="+balance+",持卡人="+name+",身份證號="+idcard+",電話="+phone;
}
    public void setName(String value) { name = value; }
    public String getName() { return name; }
	
    public void setAddress(String value) { address = value; }
    public String getAddress() { return address; }
	
    public void setIdcard(String value) { idcard = value; }
    public String getIdcard() { return idcard; }
	
    public void setPhone(String value) { phone = value; }
    public String getPhone() { return phone; }
	
    public void setCreateTime(Date value) { createTime = value; }
    public Date getCreateTime() { return createTime; }
	
    public void setLastTransactionTime(Date value) { lastTransactionTime = value; }
    public Date getLastTransactionTime() { return lastTransactionTime; }
	
    public void setInitialBalance(float value) { initialBalance = value; }
    public float getInitialBalance() { return initialBalance; }
	
    public void setBalance(float value) { balance = value; }
    public float getBalance() { return balance; }
	
    public void setAllowOverDraft(float value) { allowOverDraft = value; }
    public float getAllowOverDraft() { return allowOverDraft; }
}


TransactionRecord.java:


package credit.system;


import java.util.Date;


public class TransactionRecord {
    Date createTime; //交易發生時間
    float amount; //交易金額
    String note; //備註

    CreditCard card; //信用卡
	
    public String toString() {
return "交易記錄:持卡人="+card.name+",身份證號="+card.idcard +",交易額="+amount+",時間="+createTime;
}
    public void setCreateTime(Date value) { createTime = value; }
    public Date getCreateTime() { return createTime; }
	
    public void setAmount(float value) { amount = value; }
    public float getAmount() { return amount; }
	
    public void setNote(String value) { note = value; }
    public String getNote() { return note; }
	
    public void setCard(CreditCard value) { card = value; }
    public CreditCard getCard() { return card; }
}

實際上,這些增加getter和setter的過程有很多工具可以幫忙,比如JBuilder,或者是Together,甚至是最小而精的免費工具Gel

這樣的採用訪問器包裝私有屬性的建議,一般來說絕大多數Java開發人員都還是可以接受的。

4.3. 編輯metadata: system.jdo

這個過程,我們完全可以通過JDOGenie帶的圖形工具來完成。

我們將上面兩個類編譯以後,打開JDOGenie的workBench,即運行JDOGenie1.4.7解包後的/workbench.bat,如果是在Unix或Linux下就運行workbench.sh。注意要求你預先設置一個環境變量:JAVA_HOME,指向系統安裝的JDK的根目錄(不是bin目錄)。

我們在其中新建一個project(File-->New Project),選擇前面編譯生成的類代碼所在的根目錄中(以下簡稱CLASSPATH,兩個.class文件應該在該目錄的credit/system/子目錄中)存放這個project,因爲這個project實際上是一個配置文件(注意不是.jdo文件),一般包含一些JDO產品相關的信息,比如是否打開某些擴展功能,License號是多少等等,這個文件在運行時是需要的。我們選擇一個project名:creditSys,JDOGenie會在CLASSPATH中將這個project保存爲一個名爲“creditSys.jdogenie”的文件。

我們先設置數據庫,爲保證實用性,我們選擇MySQL作爲底層數據庫,安裝MySQL的過程很簡單,從MySQL網站下載4.0.13版本,安裝到系統中,然後啓動mysql服務即可。我們會採用其安裝後自動生成的“test”數據庫作爲本文的數據庫。

我們還需要在MySQL網站上下載jdbc驅動:版本號是3.0.7,下載後,將其ZIP包中的mysql.jar文件解出來放到某個目錄中備用。

設置數據庫的界面如下:

CSDN_Dev_Image_2003-7-21253156.jpg

配置好數據庫後,可以點擊下面的“Test”按鈕測試一下連接是否正常。可能你會看到一個找不到MySQL的JDBC驅動的錯誤提示,沒關係,我們直接點擊OK,進入project屬性配置界面,在其中將JDBC驅動加入,然後再回來測試連接即可。

project屬性配置界面基本上不需要怎麼設置,只要做兩個必須的配置:

  1. 將MySQL的JDBC驅動加入到CLASSPATH中
  2. 將我們的類代碼的根目錄加入到CLASSPATH中

界面如下:

CSDN_Dev_Image_2003-7-21253158.jpg

點擊“OK”後,我們就到了主窗口,在主窗口中,我們可以進行metadata的編輯工作。我們通過菜單Meta-->Add Classes將前面的兩個數據類加入到元數據中,表示這兩個類是需要存儲的。其餘的數據表結構等等繁瑣的事務,我們全部留給JDOGenie自動完成。添加數據類的過程中需要創建一個元數據文件,根據JDO的標準,一般情況下我們只需要在CLASSPATH根目錄下創建一個“system.jdo”即可。加入數據類後的界面如下:

CSDN_Dev_Image_2003-7-212531510.jpg

而自動生成的元數據文件system.jdo內容非常簡單,實際上直接用手工寫也不難:

<?xml version="1.0" encoding="UTF-8"?>
<jdo>
    <package name="credit.system">
        <class name="CreditCard" />
        <class name="TransactionRecord" />
    </package>
</jdo>

不過如果類比較多,之間關係也比較複雜的時候,就最好通過工具完成,以免出現語法和語義錯誤,除非你已經很有經驗。

之後,我們選擇菜單的Build-->Recreate Schema來創建相應的數據庫。數據庫的表結構是自動生成的,如果你對其中一些表名或字段名或字段長度有異議,可以在主窗口中自定義。這裏爲簡明扼要,全部採用自動生成。如果你想在建表之前看看數據結構,可以選擇菜單“Build-->View Schema”先預覽一下生成的表結構SQL代碼:

-- credit.system.CreditCard
create table credit_card (
    credit_card_id INTEGER not null,        -- 
    address VARCHAR(255),                   -- address
    allow_over_draft FLOAT,                 -- allowOverDraft
    balance FLOAT,                          -- balance
    create_time DATETIME,                   -- createTime
    idcard VARCHAR(255),                    -- idcard
    initial_balance FLOAT,                  -- initialBalance
    last_transaction_time DATETIME,         -- lastTransactionTime
    nme VARCHAR(255),                       -- name
    phone VARCHAR(255),                     -- phone
    jdo_version SMALLINT not null,          -- 
    constraint pk_credit_card primary key (credit_card_id)
) TYPE = InnoDB;

-- za.co.hemtech.jdo.server.jdbc.sql.HighLowJdbcKeyGenerator
create table jdo_keygen (
    table_name VARCHAR(64) not null,
    last_used_id INTEGER not null,
    constraint pk_jdo_keygen primary key (table_name)
) TYPE = InnoDB;

-- credit.system.TransactionRecord
create table transaction_record (
    transaction_record_id INTEGER not null, -- 
    amount FLOAT,                           -- amount
    credit_card_id INTEGER,                 -- card
    create_time DATETIME,                   -- createTime
    note VARCHAR(255),                      -- note
    jdo_version SMALLINT not null,          -- 
    constraint pk_transaction_record primary key (transaction_record_id)
) TYPE = InnoDB;

接下來,我們保存這個Project,也就是將配置信息寫入“creditSys.jdogenie”。下面我們就可以繼續開發了,也就是說,我們所有的數據類包裝工作就全部完成了,已經可以享受JDO的自動維護的對象存儲和靈活的面向對象的JDOQL查詢語言的好處了。

JDOGenie1.4.7還有一個好處,是它新增的UML類圖功能,簡潔明瞭地給出數據類之間的關係,對於理解別人的數據模型非常有用。選擇菜單“Meta-->Diagrams”,將所有的類都加到圖中,即可看到本文中的數據模型:

CSDN_Dev_Image_2003-7-212531512.jpg

如果類比較多的話,可能這些關係線段會有交叉,那就需要我們手動地調整一下各個類的位置,做到儘量減少交叉。一個複雜一點的類圖示範如下(未調整位置):

CSDN_Dev_Image_2003-7-212531514.jpg

4.4. 增強我們的數據對象類代碼

配置信息寫完了,metadata也生成好了,下面就是用JDOGenie的類代碼增強器增強我們編譯生成的類代碼。在這裏,由於需要配置一些CLASSPATH、JDO文件路徑等,我們寫了一段Ant腳本來完成這些麻煩的工作。

這個Ant腳本保存在我們的項目根目錄下,名爲“build.xml”:

<?xml version="1.0" encoding="GB2312" ?>
<project name="creditSys" default="enhance">
    <property name="src" location="src" />
    <property name="build" location="classes" />
    <property name="jdbc.jar" location="d:/bak/lib/mysql.jar" />

    <path id="cp">
        <pathelement path="${build}"/>
        <pathelement path="${jdbc.jar}"/>
        <fileset dir="." includes="*.jar" />
    </path>

    <target name="cleanup" description="清除所有編譯生成的class文件">
        <delete>
            <fileset dir="${build}" includes="**/*.class"/>
        </delete>
    </target>

    <target name="compile">
        <javac debug="true" destdir="${build}" srcdir="${src}" classpathref="cp"/>
    </target>

    <target name="enhance" depends="cleanup, compile">
        <taskdef classpathref="cp" resource="jdogenie.tasks"/>
        <jdo-enhance project="${ant.project.name}.jdogenie" outputdir="${build}" />
    </target>

    <target name="run" description="運行credit.system.Main類,測試功能">
<java classname="credit.system.Main" classpathref="cp" fork="true" />
</target>
</project>

這樣,我們需要增強類代碼的時候,在DOS框中,在build.xml所在的目錄下運行“ant”,即可完成增強過程。另外,要運行測試程序類credit.system.Main的話(後面會講到),只需要運行“ant run”即可,這樣可以解決沒有類似JBuilder的IDE的問題。

4.5. 編寫業務邏輯方法:永遠不能迴避的任務

業務邏輯,好象在面向對象編程中常常提到,但到底什麼是業務邏輯呢?難道僅僅是爲了顯示自己很高深,所以張口閉口就業務邏輯?確實有這樣一些人,談論開發的分析與設計時,脫口而出就是“業務邏輯”,實際上可能他們自己也不知道到底什麼纔是業務邏輯。

我個人的理解可能也不是很充分。我是這樣理解的:在應用中與存儲無關的對象之間的協作(即屬性的關聯性的變化)就是業務邏輯,一般體現爲一些規則,比如:當新生成一個交易時,就將信用卡的總交易次數加1,將系統的總交易次數也加1。類似這樣的規則就是業務邏輯,這些是每個應用特定的,是工具無法自動爲你實現的,必須在你自己的應用代碼中體現,不論是作爲Java代碼,還是作爲數據庫存儲過程,總之,必須有一段代碼來體現這些業務邏輯。

採用JDO解決了存儲的問題之後,我們就可以編寫工具類(獨立於數據類的包含業務邏輯方法的類)來完成需求中描述的功能了。注意我們在下面的代碼中用到的JDO API。具體API的使用參見JDO文檔以及本文末的參考文章中的《Java Data Objects第一章翻譯》。

在完成第一個功能需求之前,我們先建立一些基礎代碼以將JDO集成起來。我們寫一個名爲“credit.system.Main”的工具類(也可稱爲控制類),來提供這些基礎的方法:

package credit.system;

import javax.jdo.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws Exception {
        System.out.println("開始測試功能……");
    }

    /**
     * 本方法專用於獲取JDO API的核心對象:存儲管理器PersistenceManager
     */
    public static PersistenceManager getPersistenceManager() {
        if(pmf == null) {
            java.util.Properties p = new java.util.Properties();
            try {
                //從配置文件讀入配置信息
                p.load(Main.class.getClassLoader().getResourceAsStream("/creditSys.jdogenie"));

            } catch(IOException ex) {
                throw new RuntimeException(ex);
            }
            pmf = JDOHelper.getPersistenceManagerFactory(p);
        }

        return pmf.getPersistenceManager();
    }
    private static PersistenceManagerFactory pmf;
}
爲能讓我們的代碼能夠編譯並運行,我們還需要JDO的API包和JDOGenie的支持包,這些支持包都被合成到JDOGenie的lib目錄下的“jdogenie.jar”文件中,還有一個JTA包也是JDOGenie運行時必須的,即其lib目錄下的jta.jar。我們將這兩個jar文件拷貝到我們的項目目錄下,以便與JDOGenie的目錄劃清界限,另外,還需要將JDOGenie的license目錄下的“jdogenie.license”文件(也就是通過郵件獲取的license文件),放到我們的CLASSPATH中。當然,也可以將該license文件和JTA包一併壓縮到jdogenie.jar中,我就喜歡這樣做,這樣可以使對JDOGenie的依賴濃縮到一個jar文件中,方便在不同JDO產品間切換(因爲我一般會讓一個應用在不同的JDO產品下工作,以保證規範性和兼容性)。

4.5.1. 錄入信用卡資料

爲了放置業務邏輯方法,爲簡便起見,我們將在Main類中用靜態方法來完成這些業務功能。首先,我們寫一個方法來完成添加信用卡資料的功能:

    /**
     * 錄入新信用卡,只需要錄入必須的資料,其它的信息自動產生。
     * @return 生成的新信用卡對象
     * @throws IdCardDuplicatedException 身份證號重複,不允許創建新卡
     */
    public static CreditCard inputCard(
        String name,
        String address,
        String idcard,
        String phone,
        float initialBalance,
        float allowOverDraft
    ) throws IdCardDuplicatedException {

        CreditCard cc = new CreditCard();
        cc.setName(name);
        cc.setAddress(address);
        cc.setIdcard(idcard);
        cc.setPhone(phone);
        cc.setInitialBalance(initialBalance);
        cc.setAllowOverDraft(allowOverDraft);

        //以下是自動產生的信息:
        cc.setCreateTime(new Date());
        cc.setBalance(initialBalance); //使剛創建後的餘額等於初始餘額,這是典型的業務邏輯

        //下面將新信用卡保存到數據庫,注意其中的JDO API。
        PersistenceManager pm = getPersistenceManager();
        //先檢測是否已經有該身份證註冊的信用卡存在:
        Query q = pm.newQuery(CreditCard.class,"idcard==_newIdcard");
        q.declareParameters("String _newIdcard");
        Collection existCards = (Collection)q.execute(idcard);
        if(existCards.iterator().hasNext()) {
            throw new IdCardDuplicatedException(); //已經該身份證號存在
        }

        //身份證號沒重複,以下保存該信用卡對象:
        pm.currentTransaction().begin(); //每次對數據庫的更新必須放到事務中
        pm.makePersistent(cc);
        pm.currentTransaction().commit(); //提交新對象
        pm.close(); //釋放JDO資源

        return cc;
    }

    public static class IdCardDuplicatedException extends RuntimeException {}

下面我們運行一下這個程序,將Main.main()改寫如下:
    public static void main(String[] args) throws Exception {
        System.out.println("開始測試功能……");

        inputCard("張三","東風東路311號","223003433995431237","020-38864157",500.00f,5000.0f);
        System.out.println("信用卡已創建!");
    }

編譯,並運行(也可以在build.xml所在目錄下運行“ant run”),系統顯示:

開始測試功能……
jdbc.con.connect jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=GB2312
jdbc.stat.exec set session transaction isolation level read committed
jdbc.stat.execQuery select version()
jdbc.con.rollback
jdbc.con.close
jdbc.con.connect jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=GB2312
jdbc.stat.exec set session transaction isolation level read committed
jdbc.stat.execUpdate update jdo_keygen set last_used_id = last_used_id + ? where table_name = 'transaction_record'
jdbc.stat.execQuery select max(transaction_record_id) from transaction_record
jdbc.stat.exec insert into jdo_keygen (table_name, last_used_id) values ('transaction_record', 0)
jdbc.con.commit
jdbc.stat.execUpdate update jdo_keygen set last_used_id = last_used_id + ? where table_name = 'credit_card'
jdbc.stat.execQuery select max(credit_card_id) from credit_card
jdbc.stat.exec insert into jdo_keygen (table_name, last_used_id) values ('credit_card', 0)
jdbc.con.commit
jdbc.con.commit
jdbc.con.rollback
JDO Genie: Created RMI registry on port 2388
JDO Genie: Bound to jdogenie.jdogenie1
pm.created
jdoql.compile credit.system.CreditCard
idcard==_newIdcard
jdbc.stat.execQuery select credit_card_id, address, allow_over_draft, balance, create_time, idcard, initial_balance, last_transaction_time, nme, phone, jdo_version from credit_card where idcard = ?
jdbc.con.commit
tx.begin
tx.commit
jdbc.con.connect jdbc:mysql://localhost/test?useUnicode=true&characterEncoding=GB2312
jdbc.stat.exec set session transaction isolation level read committed
jdbc.stat.execUpdate update jdo_keygen set last_used_id = last_used_id + ? where table_name = 'credit_card'
jdbc.stat.execQuery select last_used_id from jdo_keygen where table_name = 'credit_card'
jdbc.con.commit
jdbc.stat.exec insert into credit_card (credit_card_id, address, allow_over_draft, balance, create_time, idcard, initial_balance, last_transaction_time, nme, phone, jdo_version) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
jdbc.con.commit
jdbc.con.commit
pm.closed
信用卡已創建!

以上信息說明我們的第一個功能已經正常完成,一條信用卡記錄已經生成到數據庫中。你可以通過mysql的數據庫查詢工具驗證。我們發現Main函數返回後,系統並未退出運行,爲什麼呢?原來JDOGenie在默認的狀態下打開了遠程控制的監聽服務,這對以後跟蹤WebApp的服務器狀態並進行調節時是很有用的。不過在這裏只會干擾我們的視線,姑且將它關掉:在JDOGenie的工作臺中,選擇“File-->Project Properties”菜單,然後在彈出的項目屬性配置對話框中將“Remote Access”檢查框清掉,表示禁止遠程訪問,然後點擊“OK”,並保存該Project。

然後,我們再次運行credit.system.Main,這次我們看到兩個現象:一是程序扔出了異常,表示身份證號重複,這符合我們的預期;二是系統退出了,沒有產生監聽的線程,這即是剛纔更改配置的結果。

到此爲止,我們的創建信用卡的功能便完成了,現在我們來看一看JDOGenie提供的工作臺集成的JDOQL查詢功能:在工作臺(WorkBench)中選中CreditCard類,然後選擇菜單“Run-->View Class Extent”可以查看CreditCard類的所有對象,這裏我們看到下面的結果:

CSDN_Dev_Image_2003-7-212531516.jpg

如果我們修改程序並創建了多張信用卡的話,在這個對話框中會看到更多的CreditCard類的實例對象(上圖實際上已經顯示了兩張信用卡信息。

下面我們接着實現需求描述中的其它功能,並且在輸出信息中不再顯示JDOGenie的調試跟蹤信息(實際上在項目配置中可以將這些信息屏蔽掉),如圖(將日誌級別改爲“errors”):

CSDN_Dev_Image_2003-7-212531518.jpg

4.5.2. 瀏覽信用卡信息

這個功能僅僅是一個對所有信用卡的查看。實際上前面已經看到,在工作臺中已經可以很輕鬆地完成這個功能,不過我們這裏需要在程序中完成。

我們在Main類中加入一個方法:listCards()

    /**
     * 列出系統中所有的信用卡資料
     */
    public static void listCards() {
        PersistenceManager pm = getPersistenceManager();
        for(Iterator itr = pm.getExtent(CreditCard.class,false).iterator();
				 itr.hasNext(); ) {
            Object o = itr.next();
System.out.println("DataStoreIdentity="+JDOHelper.getObjectId(o)+", "+o);
} pm.close();
}

該方法列出當前系統中的所有信用卡,包括自動生成的卡號(對象標識)。可以看到,方法的內容非常簡單。你可以不屑一顧地說:哦,原來JDOGenie的工作臺中的查看類對象的功能也不過如此而已!不錯,確實如此而已,原因很簡單:科技以人爲本,JDO的目標就是簡化操作!

另外,再將main()方法改一下:

    public static void main(String[] args) throws Exception {
        System.out.println("開始測試功能……");

        listCards();
    }

然後編譯,運行,結果如下(如果用Ant來運行,可能顯示結果會有一些Ant工具的信息,以後不再解釋):

開始測試功能……
DataStoreIdentity=1979371451-1, 信用卡:餘額=500.0,持卡人=張三,身份證號=223003433995431237,電話=020-38864157
DataStoreIdentity=1979371451-11, 信用卡:餘額=500.0,持卡人=李四,身份證號=320012194501032339,電話=020-38864157

這裏,我們可以看到幾點:一是JDOGenie的日誌功能已經關閉(前面的設置生效了),二是顯示信用卡時利用到了CreditCard中的toString()方法。俗話說,機遇只照顧有準備的頭腦,前面我們似乎是畫蛇添足地給兩個數據類加入了toString()方法,現在我們看到,這兩個方法發揮了作用!

4.5.3. 交易刷卡

這個功能應該算是本系統的基本功能,信用卡拿來幹什麼的?就是拿來刷的。當然,拿來擺闊也有一定的可能,我就聽說過有的人錢包一拿出來,N張信用卡排成一排,夠豪氣!不過可能一張都刷不了!

好了,廢話少說,我們在Main類中加入一個新的刷卡方法buySomething():

    /**
     * 用信用卡購買商品時的刷卡功能。
     * @param cardId 信用卡的標識,一般可通過刷卡機識別得到
     * @param amount 交易金額
     * @param idcard 用於驗證的身份證號,可通過收銀員輸入
     * @return 交易後的卡上餘額
     * @throws IdcardMismatchException 身份證號不符,拒絕交易
     * @throws TooMuchOverDraftException 該卡已經透支太多,不能完成本交易
     */
    public static float buySomething(String cardId, float amount, String idcard)
    throws IdcardMismatchException, TooMuchOverDraftException {
        PersistenceManager pm = getPersistenceManager();
        pm.currentTransaction().begin();
        CreditCard cc = (CreditCard)
pm.getObjectById(pm.newObjectIdInstance(CreditCard.class,cardId),false);
if(idcard == null || !idcard.equals(cc.getIdcard())) throw new IdcardMismatchException(); if(cc.getBalance()-amount < -cc.getAllowOverDraft()) throw new TooMuchOverDraftException(); TransactionRecord tr = new TransactionRecord(); tr.setCard(cc); tr.setAmount(amount); tr.setCreateTime(new Date()); tr.setNote("在天貿南大購買了一件商品"); pm.makePersistent(tr); cc.setBalance(cc.getBalance()-amount); pm.currentTransaction().commit();
        float balance = cc.getBalance();
        pm.close();

        return balance;
    }

    public static class IdcardMismatchException extends RuntimeException {}
    public static class TooMuchOverDraftException extends RuntimeException {}

然後,修改Main.main():

 
    public static void main(String[] args) throws Exception {
        System.out.println("開始測試功能……");

        float balance = buySomething("1979371451-1",250.0f,"223003433995431237");
        System.out.println("刷卡成功!餘額:"+balance);
    }

運行結果:
開始測試功能……
刷卡成功!餘額:250.0

嗯,好東西,再買一個:
開始測試功能……
刷卡成功!餘額:0.0

啊,卡上沒錢了?! 不過這是信用卡嘛,我再買!運行:
開始測試功能……
刷卡成功!餘額:-250.0

哈哈,已經透支了,不過透支上限有5000大洋呢!我再買……
算了,還是打住吧,免得銀行查下來,說我教唆用戶惡意透支,有損我個人聲譽。

4.5.4. 存款

存款,顧名思義,就是將現金存入信用卡,這樣可以刷卡買更多的商品(這也要解釋???)。

如果已經透支了,那這個時候進行的存款操作可以稱作是“補倉”,哈哈,股票的術語我們借用一下。

方法Main.deposit():

    /**
     * 存款。
     * 對於存款,我們不設最多可存多少錢的限制,因此,這個方法不扔出異常。
     * @param cardId 信用卡標識。這是通過PersistenceManager.getObjectId()得來的。
     * @param amount 存款額
     * @return 存款後的餘額
     */
    public static float deposit(String cardId, float amount) {
        //assert amount > 0; //保證存款額是正數。(可以說是廢話)
        PersistenceManager pm = getPersistenceManager();
        pm.currentTransaction().begin();
        CreditCard cc = (CreditCard)
            pm.getObjectById(pm.newObjectIdInstance(CreditCard.class,cardId),false);

        cc.setBalance(cc.getBalance()+amount);
        pm.currentTransaction().commit();
        float balance = cc.getBalance();
        pm.close();

        return balance;
    }

Main.main()改成:

    public static void main(String[] args) throws Exception {
        System.out.println("開始測試功能……");

        float balance = deposit("1979371451-1",168.0f);
        System.out.println("存款168元后,餘額是:"+balance);
    }

運行之,結果如下:
開始測試功能……
存款168元后,餘額是:-82.0

爲什麼存168塊錢呢?一來這個月遲到太多,工資被狂扣,二來,這個數字比較吉利,還可以剩點錢買碗麪吃:(。(有沒有這麼可憐啊???)

總算又過了一個月,又發工資了,再存:
開始測試功能……
存款168元后,餘額是:86.0

嘿,總算擺脫了透支的陰影,咱大老爺們的,總算能挺直腰板兒做人了!

唉,這祖宗傳下來的觀念就象三座大山,壓死人了!瞧別人老美,沒有一個不錯錢(透支)的,別人活着多精神!咱太老實,借點錢就象是前世欠別人一樣,心理負擔太沉重,搞不好性格分裂,精神崩潰啊……

4.5.5. 查詢透支報表

這個功能是查詢當前系統中已經處於透支狀態的那些信用卡,這裏我們將會用到條件過濾,也就是使用JDO的查詢語言JDOQL。在Main中加一個方法 listOverDrafts():

    /**
     * 列出已經透支的信用卡。
     */
    public static void listOverDrafts() {
        PersistenceManager pm = getPersistenceManager();
        Query q = pm.newQuery(CreditCard.class,"balance < 0"); //過濾條件
        Collection col = (Collection)q.execute();
        for(Iterator itr = col.iterator(); itr.hasNext(); ) {
            System.out.println(itr.next());
        }

        pm.close();
    }

我們注意到,經過剛纔的一系列操作後,目前還沒人透支(張三嚇出一身冷汗,幸好剛纔補了倉!)。再執行這個方法之前,我們再讓張三買點東西,改一下main()方法,還是調用:buySomething("1979371451-1",250.0f,"223003433995431237"); 結果如下:
開始測試功能……
刷卡成功!餘額:-164.0

可憐的張三又背上了沉重的心理負擔。

下面我們改一下Main.main(),調用列出透支信用卡方法:

    public static void main(String[] args) throws Exception {
        System.out.println("開始測試功能……");

        System.out.println("以下是透支的信用卡列表:");
        listOverDrafts();
    }
 

運行,結果如下:
開始測試功能……
以下是透支的信用卡列表:
信用卡:餘額=-164.0,持卡人=張三,身份證號=223003433995431237,電話=020-38864157

我們快馬加鞭,繼續挺進下一需求功能。

4.5.6. 查詢交易明細

這個功能相對複雜一點,不過也只是理解上覆雜,代碼還是很簡單的。我們在Main中增加一個方法 listTransactions():

    /**
     * 列出某信用卡的交易明細。
     * @param idcard 身份證號
     */
    public static void listTransactions(String idcard) {
        PersistenceManager pm = getPersistenceManager();
        Query q = pm.newQuery(TransactionRecord.class,
            "card.idcard==_p0"); //過濾條件
        q.declareParameters("String _p0"); //聲明查詢參數表
        q.setOrdering("createTime ascending"); //按時間順序列出

        Collection col = (Collection)q.execute(idcard); //按指定身份證號查詢
        for(Iterator itr = col.iterator(); itr.hasNext(); ) {
            System.out.println(itr.next());
        }
        pm.close();
    }

在這個方法中,我們用到了JDOQL最吸引人的特性之一:對象引用,即“card.idcard==_p0”,這樣實際上相當於兩個數據表聯表查詢,顯然,這樣的語句更易理解,更簡潔!此外,我們用到了查詢參數“_p0”,最後還用到了排序。實際上,還可以按此類所引用的其它類的屬性進行排序,比如按持卡人姓名順序列出所有交易記錄,排序語句將是:q.setOrdering("card.name ascending"); 又省下表連接的冗長SQL語句。

最後,我們再修改Main.main()方法:

 
    public static void main(String[] args) throws Exception {
        System.out.println("開始測試功能……");

        System.out.println("以下是張三的交易記錄:");
        listTransactions("223003433995431237");
    }

運行之,結果如下:
開始測試功能……
以下是張三的交易記錄:
交易記錄:持卡人=張三,身份證號=223003433995431237,交易額=250.0,時間=Mon Jun 30 09:24:51 GMT 2003
交易記錄:持卡人=張三,身份證號=223003433995431237,交易額=250.0,時間=Mon Jun 30 09:25:34 GMT 2003
交易記錄:持卡人=張三,身份證號=223003433995431237,交易額=250.0,時間=Mon Jun 30 09:27:24 GMT 2003
交易記錄:持卡人=張三,身份證號=223003433995431237,交易額=250.0,時間=Mon Jun 30 09:29:15 GMT 2003
交易記錄:持卡人=張三,身份證號=223003433995431237,交易額=250.0,時間=Mon Jun 30 09:29:53 GMT 2003
交易記錄:持卡人=張三,身份證號=223003433995431237,交易額=250.0,時間=Mon Jun 30 10:35:57 GMT 2003

這些就是我們前面的幾次調用產生的交易記錄。實際上,存款記錄也應該算是交易記錄,只不過本系統中暫時沒有這種需求。(注意了,以後提需求的時候要多加考慮才行,否則又會讓開發商鑽空子了!)

4.6. 讓程序更實用些

上面我們已經完成了所有的需求功能,大家也看到了基於JDO的應用是如何開發的,需要哪些配置。我再次說明一下,JDOGenie是最方便學習JDO的工具,儘管每隔一個月你得去重新索取一個試用license。

話又說回來,上面這些完成業務邏輯的方法太簡單了,不被開發主管打纔怪!(參見:程序寫得爛被主管狂扁!

爲了讓程序更實用,我們需要將其改造成一個真正的應用,這就涉及到安全性、性能優化與索引創建、容易操作的界面、後臺操作日誌、備份、圖文並茂的報表,等等等等。儘管這些已經超出本文的範圍,我們下面還是進行一點簡單的討論。

一般有兩種最常見的方式做這樣的系統:獨立的GUI程序或者基於瀏覽器的Web應用。下面分別給出兩種方式下的建議:

4.6.1. 圖形界面的獨立程序

首先,我們要從java.awt.Frame擴展出一個子類,在其窗口中放入幾個Panel,分別對應需求中的幾項功能,如果該功能是需要輸入參數的,則在相應的Panel中放幾個TextField,然後給一個提交按鈕;如果該功能不需要輸入參數,則直接給一個功能按鈕即可。每個按鈕的事件響應中,將相關的參數從界面元素中讀出,傳入到相應的方法中,然後接收方法的返回,將結果打印在界面的輸出區(對了,還需要在界面上加一個輸出區域,比如TextArea)。

前面我們的Main類的業務邏輯方法中,很多地方都是直接打印了查詢取得的數據,在獨立GUI程序中不能這樣做,只能將查詢取得的數據(如Collection)返回到調用的按鈕事件中,再由按鈕事件處理的代碼打印到輸出區域。這也算是一種MVC的方式了,呵呵。

還有一點建議,由於pm.close()後,對象的屬性不能保證準確讀出,所以,在獨立的GUI程序中,最好系統一直保留一個打開的pm,各個方法共享。

4.6.2. 採用JSP的Web應用

基於JSP的Web應用相對複雜一些,我們需要將jdogenie.jar(合併了jta.jar和其license文件的包)和mysql.jar放到應用的/WEB-INF/lib中,然後將自己的enhance過的類代碼按目錄結構放到/WEB-INF/classes/中,同時配置文件creditSys.jdogenie和system.jdo一併放到/WEB-INF/classes/目錄中。

這些只是目錄的配置,我們還需要對前面的代碼進行改造。

首先,與獨立的GUI程序一樣,需要將直接通過System.out.println()輸出的業務邏輯方法改爲將查詢所得的Collection或具體對象返回到調用者。這裏的調用者就是JSP。我們可以對每個功能寫一到兩個jsp,這些JSP只完成數據輸入和顯示的作用,這一點與獨立的程序差不多。

不過,由於Web應用是多線程的,就需要考慮到併發連接的問題,因此係統中不能只設置一個始終打開的PM,而是每個頁面請求需要使用一個PM。爲了能在一個頁面請求的處理過程中在不同的代碼片斷裏共享一個PM,你可以參考我在JDOCentral的論壇上發表的這篇貼子

關於Web應用中PM連接池和資源釋放的細節已經超出本文的範圍,請參考我在CSDN上發表的其它關於JDO的文章(見本文尾部的“參考文章”)。

5. 總結

下面我們對以上的基於JDO的開發作一點總結。

5.1. 開發流程的變化

基於JDO開發的流程在本文開始處通過一幅圖進行了介紹,主要增加了兩個步驟:編寫metadata,和根據metadata對類代碼進行增強。而這兩步通過JDOGenie的工作臺很容易搞定,都是自動化的。

剩下的開發過程就比原始的JDBC包裝簡單多了,我們不再有SQL代碼,不再有複雜的“增、刪、改、查”通用接口,不再有不支持事務的擔心,不再有……我們只需要在一個完全面向對象的數據對象模型上通過JDO的API進行操作即可。

另外一點,我們可以看到,基於JDO的開發中,各種開發角色(JavbaBean編寫、JSP編寫、數據庫維護、配置)都是基於數據對象模型進行開發,比如說,用JDOGenie就可以生成數據模型的UML類圖後,打印下來,交給各個開發參與人員,無論是寫控制類JavaBean,還是JSP,還是數據庫結構維護,都可以以這個圖作基準,大家統一進行開發,而不是象基於JDBC的應用,大家都基於數據庫結構進行開發。

JDO給我們帶來的太多了,需要在開發過程中慢慢體會。

5.2. 速度會不會慢?--性能測試數據參考

我使用過一個有一定規模的應用來測試過JDO的性能,這個應用具有十多個類,每個類有5~20個屬性,類之間有錯綜複雜的關係,功能很多,是一個完整的基於瀏覽器的Web應用。使用原始的自己的JDBC包裝時,數據庫相關的處理代碼非常多,有10多K,而改造JDO後,這些代碼都沒了,實際上,相當於這些代碼變成了JDO廠商的支持包。

接着是性能測試,原來的基於JDBC的應用每秒可處理8個請求,而採用JDOGenie作底層的JDO應用每秒可處理35個請求,採用KodoJDO作底層的JDO應用(手寫的Java代碼與JDOGenie一樣)時,每秒可處理25個請求。這一切都歸功於JDO規範所規定的延遲讀取機制(Lazy Loading Mechanism)。

可能有眼尖的讀者會問:爲什麼文章開頭說Kodo是性能王者,而這裏卻不如JDOGenie呢?不錯,眼睛實在是雪亮!這樣的材質,怎麼不去做私家偵探啊!搞個什麼X美鳳的片子,肯定一炮走紅!
哦,對了,我還沒給出解釋。我前面說Kodo性能最好,是包括對穩定性的評價在內的。我這裏對兩者的測試,都是採用了JDO規範定義的樂觀事務方式(javax.jdo.option.OptimisticTransaction),這種方式將事務的鎖定限制在應用端,不對數據庫造成壓力,只是在對同一對象的併發改動很多的時候會導致很多衝突。一般來說,你的Web應用不會一天訪問量上百萬吧?如果不到,就完全可以採用這種事務處理方式。Kodo的事務處理在測試中成功機會比JDOGenie大很多,同樣的測試時間,Kodo的類似訪問數的統計數據比JDOGenie要多,也就是說Kodo的成功事務比JDOGenie多,儘管總訪問數少於JDOGenie。綜上所述,Kodo的性能還是要比JDOGenie好。不過JDOGenie也在奮起直追,我對它比較看好!

5.3. 還有些不爽--JDO1.0的侷限性

好了,興奮過後,應該是冷靜的反思。

有一句話說得好,最值得相信的人,只有你自己!

我是一名JDO的狂熱追隨者,爲推廣JDO做着不懈的努力,當然免不了某些時候言語過激或片面化,所謂“一葉障目,不見泰山”。就象Java追隨者認爲.NET一無是處一樣,實際上.NET有很多優點是Java無法相比的,比如快速,靈活。

我也冷靜下來,仔細地思考了JDO1.0的不足之處,竟發現有很多:

  1. 增加額外步驟,配置複雜(相對於直接的JDBC)
  2. 對數據模型有一定限制(必須有一個無參構造器,屬性訪問需要getter和setter)
  3. 雙向對象關係的處理太欠缺(JDO2.0計劃中的自動維護的對象關係將解決這些)
  4. JDOQL的API稍顯累贅(declare一大堆東西,比ODMG的OQL標準還是不如)
  5. 沒有數據庫統計功能(count(),max(),avg()等等,不過已經在JDO2.0計劃中)

5.4. JDO2.0展望

Sun的動作就是慢,象一個遲暮的巨人。JDO1.0討論了四年才半推半就地現身,JDO2.0又還未正式立項,真不知何年何月才能真正解決數據庫應用中的所有問題!不過羣衆的參與就是推動力,讓我們一起提出我們的意見,一起推動JDO2.0的車輪吧!(我們可以對JDO規範制定人的郵箱狂轟濫炸,直到他們說“馬上開始制定”或者“算了,給你十萬$,饒了我吧”爲止)。

6. 參考文章(中文)

  實際上下面列出的文章基本上都可以直接在www.CSDN.net上通過搜索關鍵字“JDO”的技術文檔來找到。

    1. 《JDO對開發的幫助有哪些》
    2. 《JDO能給我們帶來什麼》
    3. 《JDO資源介紹》
    4. 《一個JDO的成功案例分析》
    5. 《一個紐約女技術員的JDO經驗》
    6. 《最權威的JDO新書》
    7. 《Java Data Objects第一章翻譯》
    8. 《選擇CMP還是JDO》

還可以在CSDN上找到一些其它作者寫的文章。

7. 參考資料

    1. JDO規範的主頁--JSR12,http://jcp.org/en/jsr/detail?id=012
    2. Sun網站中作爲J2SE optional pack的網頁--http://java.sun.com/products/jdo/
    3. 核心推廣網站--http://www.jdocentral.com
    4. 最集中的討論區--http://www.jdocentral.com/forums/index.php
    5. 相關中文資源網之一--http://www.CSDN.net
    6. 相關中文資源網之二--http://www.JavaResearch.org
    7. Versant的一份介紹材料:http://www.jdocentral.com/pdf/JavaDataObjects_McCammon.pdf
    8. 綜合資料:http://www.jdocentral.com/JDO_Resources_Body.html

8. 參考書籍:

以下信息來自:http://www.jdocentral.com/JDO_Resources_Body.html

    1. 《Java Data Objects》,作者:Robin Roos(英國)
    2. 《Java Data Objects》,作者:Craig Russell(美國) 和 David Jordan(美國)
    3. 《Core Java Data Objects》,作者:很多人,由Sun組織
    4. 《Using and Understanding Java Data Objects》 作者:David Ezzio(美國)
    5. 其它一些PDF資料:http://www.javaresearch.org/dn/JDO_introduce.zip

本文的版權屬於筆者本人,但歡迎轉載,前提是註明出處和原作者。另外,歡迎在我的專欄中查看我的另幾篇文章,並提出寶貴意見!

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