前言
在 開源面向?qū)ο髷?shù)據(jù)庫 db4o 之旅 系列文章的第一部分:初識 db4o 中,作者介紹了 db4o 的歷史和現(xiàn)狀,應(yīng)用領(lǐng)域,以及和 ORM 等的比較。在這篇文章中,作者將會介紹 db4o 的安裝、啟動以及三種不同的查詢方式:QBE(Query by Example)、SODA(Simple Object Database Access) 以及 NQ(Native Queries),并分別通過這三種不同的途徑實現(xiàn)了兩個關(guān)聯(lián)對象的查詢。本文還示范了開發(fā)中最經(jīng)常用到的幾個典型功能的 db4o 實現(xiàn)。
下載和安裝 db4o
db4o 所有最新的版本都可以直接在官方網(wǎng)站上下載,進(jìn)入 db4o 的下載頁面,我們可以看到最新的 for Java 穩(wěn)定版本是 5.5,包括 JAR、源代碼、入門文檔、API 等內(nèi)容的完整的打包文件只有 6 MB,db4o 還有一個對象數(shù)據(jù)庫管理工具 ObjectManager,目前版本是 1.8(請在參考資源中下載)。
接著在 Eclipse 中新建 Java 項目,把 db4o 對象數(shù)據(jù)庫引擎包 db4o-5.5-java5.jar 導(dǎo)入進(jìn)項目。由于 db4o 支持多種版本的 JDK,除了 for JDK 5.0 的 db4o-5.5-java5.jar 外,還有 for JDK 1.1、1.2-1.4 的 JAR 包,以適應(yīng)多種環(huán)境。與 Hibernate、iBATIS SQL Maps 相比,db4o 更加自然,無需過多地引用第三方支持庫。
開啟數(shù)據(jù)庫
db4o 怎樣進(jìn)行對象持久化呢?通過瀏覽目錄可以發(fā)現(xiàn),與傳統(tǒng)的 RDBMS 一樣,db4o 也有自己的數(shù)據(jù)庫文件, 在 db4o 中數(shù)據(jù)庫文件的后綴名是“*.yap”。讓我們先來了解一下 db4o 對象數(shù)據(jù)庫引擎的主要包結(jié)構(gòu):
db4o 提供兩種運行模式,分別是本地模式和服務(wù)器模式。本地模式是指直接在程序里打開 db4o 數(shù)據(jù)庫文件進(jìn)行操作:
ObjectContainer db = Db4o.openFile("auto.yap");
而服務(wù)器模式則是客戶端通過 IP 地址、端口以及授權(quán)口令來訪問服務(wù)器:
ObjectServer server=Db4o.openServer("auto.yap",1212); server.grantAccess("admin","123456");
ObjectContainer db=Db4o.openClient("192.168.0.10",1212,"admin","123456");
兩種方式都可以得到 ObjectContainer 實例,就目前 Java EE 應(yīng)用環(huán)境來看,服務(wù)器模式更有現(xiàn)實意義;而本地模式更適合于嵌入式應(yīng)用。為了簡化演示,本文在下面的例子都將采用本地模式。
在下面的例子里,我們都會用到下面兩個對象: People 和 AutoInfo 對象。
People 對象:
package bo; public class People { private java.lang.Integer _id; private java.lang.String _name; private java.lang.String _address; private java.util.List<AutoInfo> _autoInfoList; public java.lang.Integer getId() { return _id; } public void setId(java.lang.Integer _id) { this._id = _id; } public java.lang.String getName() { return _name; } public void setName(java.lang.String _name) { this._name = _name; } public java.lang.String getAddress() { return _address; } public void setAddress(java.lang.String _address) { this._address = _address; } public java.util.List<AutoInfo> getAutoInfoList() { return this._autoInfoList; } public void addAutoInfo(AutoInfo _autoInfoList) { if (null == this._autoInfoList) this._autoInfoList = new java.util.ArrayList<AutoInfo>(); this._autoInfoList.add(_autoInfoList); } }
AutoInfo 對象:
package bo; public class AutoInfo{ private java.lang.Integer _id; private java.lang.String _licensePlate; private bo.People _ownerNo; public java.lang.Integer getId () { return _id; } public void setId (java.lang.Integer _id) { this._id = _id; } public java.lang.String getLicensePlate () { return _licensePlate; } public void setLicensePlate (java.lang.String _licensePlate) { this._licensePlate = _licensePlate; } public bo.People getOwnerNo () { return this._ownerNo; } public void setOwnerNo (bo.People _ownerNo) { this._ownerNo = _ownerNo; } }
利用 set 方法把新對象存入 ObjectContainer,而對 ObjectContainer 中已有對象進(jìn)行 set 操作則是更新該對象。db4o 保存數(shù)據(jù)庫很簡單,下面就是一個段完整的保存對象的代碼:
package com; import bo.AutoInfo; import bo.People; import com.db4o.Db4o; import com.db4o.ObjectContainer; public class DB4OTest{ public static void main(String[] args){ //打開數(shù)據(jù)庫 ObjectContainer db = Db4o.openFile("auto.yap"); try{ //構(gòu)造 People 對象 People peo = new People(); peo.setId(1); peo.setAddress("成都市"); peo.setName("張三"); //構(gòu)造 AutoInfo 對象 AutoInfo ai = new AutoInfo(); ai.setId(1); ai.setLicensePlate("川A00000"); //設(shè)置 People 和 AutoInfo 的關(guān)系 ai.setOwnerNo(peo); peo.addAutoInfo(ai); //保存對象 db.set(peo); }finally{ //關(guān)閉連接 db.close(); } } }
當(dāng)我們運行上述代碼,db4o 會自動創(chuàng)建“auto.yap”文件。讓我們來看看到底保存成功沒有,打開 ObjectManager 工具,圖 1 所示。
“File”->“Open File”->選擇剛才我們保存的“auto.yap”文件(“auto.yap”文件可在項目的根目錄下找到),最新的 ObjectManager 1.8 版本為我們提供了“Read Only”方式讀取數(shù)據(jù)庫文件,避免 ObjectManager 占用數(shù)據(jù)庫文件所導(dǎo)致的程序異常。
打開之后,圖 2 所示,剛才存貯的 People 對象已經(jīng)在數(shù)據(jù)庫中了,并且還可以很直觀的看到 AutoInfo 對象也放入了 ArrayList 中。這種可視化的對象關(guān)系有利于我們對數(shù)據(jù)的理解,是傳統(tǒng) RDBMS 無法比擬的。有些開發(fā)者會說 ObjectManager 工具略顯簡單,這點我想隨著 db4o 的不斷發(fā)展會加入更多的特性。在這個工具中,我們意外的發(fā)現(xiàn)了 Java 集合對象的蹤影,db4o 把與 ArrayList 有直接關(guān)系的所有接口和父類都保存了,這樣顯得更直觀。
在此,我保留了 _id 屬性,這是因為通常在 Java EE 環(huán)境中,DAO 第一次不是把整個對象都返回到表現(xiàn)層,而是只返回了“標(biāo)題”、“發(fā)布時間”這些信息(并隱式的返回id),接著 DAO 與數(shù)據(jù)庫斷開;要查看詳情(比如文章內(nèi)容)就需要進(jìn)行 findById 操作,這時 DAO 要再次與數(shù)據(jù)庫交互,只有唯一標(biāo)識符才能正確地找到對象。這種懶加載方式也是很多書籍所推薦的。
回到本文的范例程序中,這個 _id 屬性可由人工編碼實現(xiàn)的“序列”進(jìn)行賦值,當(dāng)然 db4o 也提供了內(nèi)部標(biāo)識符 Internal IDs,圖 2 中的 id=1669;以及 UUIDs。
查詢數(shù)據(jù)庫
和 RDBMS 一樣,db4o 也有自己的查詢語言,分別是 QBE(Query by Example)、NQ(Native Queries)、SODA(Simple Object Database Access),db4o 更推薦使用 NQ 進(jìn)行查詢。NQ 方式提供了非常強(qiáng)大的查詢功能,支持原生語言,也就意味著你可以使用 Java 來判斷該對象是否符合條件,這是其他數(shù)據(jù)庫查詢語言無法比擬的。在某些情況下, db4o 核心會將 NQ 翻譯成 SODA 以獲得更高的性能。下面詳細(xì)介紹一下這三種查詢語言。
QBE(Query by Example)
QBE 規(guī)范可在這里下載。QBE 最初由 IBM 提出,同時業(yè)界也有許多和 QBE 兼容的接口,包括著名的 Paradox。有些系統(tǒng),比如微軟的 Access,它的基于表單的查詢也是受到了部分 QBE 思想的啟發(fā)。在 db4o 中,用戶可借用 QBE 快速上手,可以很容易適應(yīng) db4o 存取數(shù)據(jù)的方式。
當(dāng)利用 QBE 為 db4o 提供模板(example)對象時,db4o 將返回所有和非默認(rèn)值字段匹配的全部對象。內(nèi)部是通過反射所有的字段和構(gòu)造查詢表達(dá)式(所有非默認(rèn)值字段結(jié)合”AND”表達(dá)式)來實現(xiàn)。
例如,利用 QBE 查找到車牌號為“川A00000”的車主姓名,這是一個級聯(lián)查詢。
package com; import java.util.List; import bo.AutoInfo; import com.db4o.Db4o; import com.db4o.ObjectContainer; public class DB4OTest{ public static void main(String[] args){ //打開數(shù)據(jù)庫 ObjectContainer db = Db4o.openFile("auto.yap"); try{ //構(gòu)造模板對象 AutoInfo ai = new AutoInfo(); ai.setLicensePlate("川A00000"); //查詢對象 List<AutoInfo> list = db.get(ai); for(int x = 0; x < list.size(); x++){ System.out.println("車主姓名:"+list.get(x).getOwnerNo().getName()); } }finally{ //關(guān)閉連接 db.close(); } } }
但是 QBE 也有明顯的限制:db4o 必須反射模板(example)對象的所有成員;無法執(zhí)行更進(jìn)一步的查詢表達(dá)式(例如 AND、OR、NOT 等等);不能約束 0(整型)、””(空字符串)或者 null(對象),因為這些都被認(rèn)為是不受約束的。要繞過這些限制,db4o 提供了 NQ(Native Queries)。
SODA(Simple Object Database Access)
SODA ,簡單對象數(shù)據(jù)庫訪問,請查看官方站點,其中一位主要維護(hù)者是 Carl Rosenberger,Carl 正是 db4o 首席架構(gòu)師。
SODA 就是一種與數(shù)據(jù)庫通訊的對象 API。最終的目標(biāo)是實現(xiàn)類型安全、對象復(fù)用、最小的字符串使用、與編程語言無關(guān)等特性。SODA 是 db4o 最底層的查詢 API,目前 SODA 中使用字符串來定義字段,這樣將不能實現(xiàn)類型安全也無法在編譯時檢查代碼,而且寫起來較麻煩,當(dāng)然要達(dá)到設(shè)計目標(biāo)這個階段是必須的。大部分情況下 NQ(Native Queries)是很好的查詢接口,不過遇到動態(tài)生成查詢的時候 SODA 就大有作為了。
通過 SODA 查找到車牌號為“川A00000”的車主姓名:
package com; import java.util.List; import bo.AutoInfo; import com.db4o.Db4o; import com.db4o.ObjectContainer; import com.db4o.query.Query; public class DB4OTest{ public static void main(String[] args){ //打開數(shù)據(jù)庫 ObjectContainer db = Db4o.openFile("auto.yap"); try{ //構(gòu)造查詢對象 Query query=db.query(); //設(shè)置被約束實例 query.constrain(AutoInfo.class); //設(shè)置被約束實例的字段和約束條件 query.descend("_licensePlate").constrain("川A00000"); //查詢對象 List<AutoInfo> list = query.execute(); for(int x = 0; x < list.size(); x++){ System.out.println("車主姓名:"+list.get(x).getOwnerNo().getName()); } }finally{ //關(guān)閉連接 db.close(); } } }
通過 API,發(fā)現(xiàn) Query 實例增加了 sortBy 按字段排序方法和 orderAscending正序、orderDescending 倒序排列方法,SODA 比 QBE 更進(jìn)了一步。
NQ(Native Queries)
精彩總是在最后出場,NQ 才是 db4o 查詢方式中最精彩的地方!有沒有想過用你熟悉的的編程語言進(jìn)行數(shù)據(jù)庫查詢呢?要是這樣,你的查詢代碼將是 100% 的類型安全、100% 的編譯時檢查以及 100% 的可重構(gòu),很奇妙吧?NQ 可以做到這些。
有兩篇論文專門講解了 NQ 的基本概念和設(shè)計思路,分別是 《Cook/Rosenberger,持久對象原生數(shù)據(jù)庫查詢語言》 和 《Cook/Rai,Safe Query Objects: Statically Typed Objects as Remotely Executable Queries》。作為結(jié)果集的一部分,NQ 表達(dá)式必須返回 true 值來標(biāo)記特定實例。如果可能的話 db4o 將嘗試優(yōu)化 NQ 表達(dá)式,并依賴索引來運行表達(dá)式。
通過 NQ 查找到車牌號為“川A00000”的車主姓名:
package com; import java.util.List; import bo.AutoInfo; import com.db4o.Db4o; import com.db4o.ObjectContainer; import com.db4o.query.Predicate; public class DB4OTest{ public static void main(String[] args){ //打開數(shù)據(jù)庫 ObjectContainer db = Db4o.openFile("auto.yap"); try{ List <AutoInfo> list = db.query(new Predicate<AutoInfo>() { public boolean match(AutoInfo ai) { //這樣才是類型安全的 return ai.getLicensePlate().equals("川A00000"); } }); for(int x = 0; x < list.size(); x++){ System.out.println(list.get(x).getOwnerNo().getName()); } }finally{ //關(guān)閉連接 db.close(); } } }
必須指出 NQ 的一個的問題是:在內(nèi)部,db4o 設(shè)法把 NQ 轉(zhuǎn)換成 SODA。但并不是所有的查詢表達(dá)式都可以成功轉(zhuǎn)換。有些查詢表達(dá)式的流向圖(flowgraph)非常難于分析。這種情況下,db4o 將不得不實例化一些持久對象來真實地運行 NQ 表達(dá)式。
正在開發(fā)中的 NQ 查詢優(yōu)化器就可以化解這個障礙,它將分析 NQ 表達(dá)式的每個部分,以確保最少量的實例化對象,以此提高性能。當(dāng)然,優(yōu)化器的不是靈丹妙藥,關(guān)鍵還需要自己多優(yōu)化代碼。
開發(fā) Java EE 項目經(jīng)常會用到分頁,怎樣用 NQ 實現(xiàn)呢?向數(shù)據(jù)庫寫入六條記錄:
package com; import java.util.List; import bo.AutoInfo; import com.db4o.Db4o; import com.db4o.ObjectContainer; import com.db4o.query.Predicate; public class DB4OTest{ public static void main(String[] args){ //打開數(shù)據(jù)庫 ObjectContainer db = Db4o.openFile("auto.yap"); try{ List<AutoInfo> list = db.query(new Predicate<AutoInfo>() { public boolean match(AutoInfo ai) { return true; } }); //記錄總數(shù) Integer count = list.size(); //每頁兩條,分三頁 for(int x = 0; x < 3; x++){ System.out.println("第"+x+"頁:"+list.get(x*2).getLicensePlate()); System.out.println("第"+x+"頁:"+list.get(x*2+1).getLicensePlate()); } }finally{ //關(guān)閉連接 db.close(); } } }
我們發(fā)現(xiàn),在進(jìn)行 NQ 查詢時并沒有加入任何條件(無條件返回 true),是不是相當(dāng)于遍歷了整個數(shù)據(jù)庫?db4o 的設(shè)計者早就想到了這個問題,當(dāng) db.query() 執(zhí)行完畢返回 list 實例的時候,db4o 只是與數(shù)據(jù)庫同步取出內(nèi)部 IDs 而已,并沒有把所有的 AutoInfo 對象全部取出,只有在 list.get(x*2).getLicensePlate() 之后才會去根據(jù) IDs 取出記錄。所以不必?fù)?dān)心性能問題。
結(jié)論
db4o 為開發(fā)者提供了多種查詢方式,這些方式都很靈活。要引起大家注意的是:靈活在帶來便利的同時也對開發(fā)者自身素質(zhì)提出了更高的要求,(比如排序,既可以用 SODA 也可以用 Java 集合對象實現(xiàn))在開發(fā)過程中一定要形成某種統(tǒng)一的開發(fā)模式,這樣 db4o 才能最高效能地為我所用。
參考資料
作者簡介
? Rosen Jiang 來自成都,是 db4o 和 OO 的忠實 fans,是 2005 年 db4o 的 dvp 獲得者之一。他正在 J2me 應(yīng)用中使用 db4o,你可以通過 rosener_722@hotmail.com 和他聯(lián)系。
? Chris 來自香港,熱愛開源和 db4o。他創(chuàng)辦了中國最火熱的 Java 和開源社區(qū) Matrix(http://www.Matrix.org.cn), 你可以通過 chris@Matrix.org.cn 和他聯(lián)系。?? 張黃矚,熱愛開源軟件,熟悉 Java/C/C++ 編程語言,對數(shù)據(jù)庫技術(shù)網(wǎng)絡(luò)技術(shù)均感興趣。你可以通過 zhanghuangzhu@gmail.com 聯(lián)系他。IBM DeveloperWorks(IBM DW) 版權(quán)所有!引用、轉(zhuǎn)貼本文應(yīng)注明本文來自 IBM DW。
Powered by: BlogJava Copyright © Rosen