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