在 db4o 之旅 系列文章的第一部分:初識 db4o 中,做者介紹了 db4o 的歷史和現狀,應用領域,以及和 ORM 等的比較。在這篇文章中,做者將會介紹 db4o 的安裝、啓動以及三種不一樣的查詢方式:QBE(Query by Example)、SODA(Simple Object Database Access) 以及 NQ(Native Queries),並分別經過這三種不一樣的途徑實現了兩個關聯對象的查詢。本文還示範了開發中最常常用到的幾個典型功能的 db4o 實現。html
db4o 全部最新的版本均可以直接在官方網站上下載,進入 db4o 的下載頁面,咱們能夠看到最新的 for Java 穩定版本是 5.5,包括 JAR、源代碼、入門文檔、API 等內容的完整的打包文件只有 6 MB,db4o 還有一個對象數據庫管理工具 ObjectManager,目前版本是 1.8(請在參考資源中下載)。java
接着在 Eclipse 中新建 Java 項目,把 db4o 對象數據庫引擎包 db4o-5.5-java5.jar 導入進項目。因爲 db4o 支持多種版本的 JDK,除了 for JDK 5.0 的 db4o-5.5-java5.jar 外,還有 for JDK 1.一、1.2-1.4 的 JAR 包,以適應多種環境。與 Hibernate、iBATIS SQL Maps 相比,db4o 更加天然,無需過多地引用第三方支持庫。數據庫
db4o 怎樣進行對象持久化呢?經過瀏覽目錄能夠發現,與傳統的 RDBMS 同樣,db4o 也有本身的數據庫文件, 在 db4o 中數據庫文件的後綴名是「*.yap」。讓咱們先來了解一下 db4o 對象數據庫引擎的主要包結構:編程
- com.db4o
com.db4o 包含了使用 db4o 時最常常用到的功能。兩個最重要的接口是 com.db4o.Db4o 和 com.db4o.ObjectContainer。com.db4o.Db4o 工廠是運行 db4o 的起點,這個類中的靜態方法能夠開啓數據庫文件、啓動服務器或鏈接一個已經存在的服務器,還能夠在開啓數據庫以前進行 db4o 環境配置。com.db4o.ObjectContainer 接口很重要,開發過程當中 99% 的時間都會用到它,ObjectContainer 可在單用戶模式下做爲數據庫實例,也可做爲 db4o 服務器的客戶端。每一個 ObjectContainer 實例都有本身的事務。全部的操做都有事務保證。當打開 ObjectContainer,就已經進入事務了,commit() 或 rollback() 時,下一個事務當即啓動。每一個 ObjectContainer 實例維護它本身所管理的已存儲和已實例化對象,在須要 ObjectContainer 的時候,它會一直保持開啓狀態,一旦關閉,內存中數據庫所引用的對象將被丟棄。
- com.db4o.ext
你也許想知道爲何在 ObjectContainer 中只能看見不多的方法,緣由以下:db4o 接口提供了兩個途徑,分別在 com.db4o 和 com.db4o.ext 包中。這樣作首先是爲了讓開發者能快速上手;其次爲了讓其餘產品能更容易的複製基本的 db4o 接口;開發者從這一點上也能看出 db4o 是至關輕量級的。每一個 com.db4o.ObjectContainer 對象也是 com.db4o.ext.ExtObjectContainer 對象。能夠轉換成 ExtObjectContainer 得到更多高級特性。
- com.db4o.config
com.db4o.config 包含了全部配置 db4o 所需的類。
- com.db4o.query
com.db4o.query 包包含了構造「原生查詢, NQ(Native Queries)」所需的 Predicate 類。NQ 是 db4o 最主要的查詢接口。
db4o 提供兩種運行模式,分別是本地模式和服務器模式。本地模式是指直接在程序裏打開 db4o 數據庫文件進行操做:安全
ObjectContainer db = Db4o.openFile("auto.yap");
而服務器模式則是客戶端經過 IP 地址、端口以及受權口令來訪問服務器:服務器
服務器端:架構
-
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 應用環境來看,服務器模式更有現實意義;而本地模式更適合於嵌入式應用。爲了簡化演示,本文在下面的例子都將採用本地模式。工具
在下面的例子裏,咱們都會用到下面兩個對象: People 和 AutoInfo 對象。性能
People 對象清單1:
清單1. People 對象
-
package bo;
-
-
public
class People
{
-
-
-
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 對象清單2:
清單2. AutoInfo 對象
-
package bo;
-
-
public
class AutoInfo
{
-
-
-
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 中已有對象進行 set 操做則是更新該對象。db4o 保存數據庫很簡單,下面就是一個段完整的保存對象的代碼:
AutoInfo 對象清單3:
清單3
-
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
)
{
-
//打開數據庫
-
ObjectContainer db = Db4o.
openFile
(
"auto.yap"
);
-
try
{
-
//構造 People 對象
-
People peo =
new People
(
);
-
peo.
setId
(
1
);
-
peo.
setAddress
(
"成都市"
);
-
peo.
setName
(
"張三"
);
-
//構造 AutoInfo 對象
-
AutoInfo ai =
new AutoInfo
(
);
-
ai.
setId
(
1
);
-
ai.
setLicensePlate
(
"川A00000"
);
-
//設置 People 和 AutoInfo 的關係
-
ai.
setOwnerNo
(peo
);
-
peo.
addAutoInfo
(ai
);
-
//保存對象
-
db.
set
(peo
);
-
}
finally
{
-
//關閉鏈接
-
db.
close
(
);
-
}
-
}
-
}
當咱們運行上述代碼,db4o 會自動建立「auto.yap」文件。讓咱們來看看到底保存成功沒有,打開 ObjectManager 工具,如圖 1 所示。
圖1. 對象數據庫管理工具
「File」->「Open File」->選擇剛纔咱們保存的「auto.yap」文件(「auto.yap」文件可在項目的根目錄下找到),最新的 ObjectManager 1.8 版本爲咱們提供了「Read Only」方式讀取數據庫文件,避免 ObjectManager 佔用數據庫文件所致使的程序異常。
打開以後,如圖 2 所示,剛纔存貯的 People 對象已經在數據庫中了,而且還能夠很直觀的看到 AutoInfo 對象也放入了 ArrayList 中。這種可視化的對象關係有利於咱們對數據的理解,是傳統 RDBMS 沒法比擬的。有些開發者會說 ObjectManager 工具略顯簡單,這點我想隨着 db4o 的不斷髮展會加入更多的特性。在這個工具中,咱們意外的發現了 Java 集合對象的蹤跡,db4o 把與 ArrayList 有直接關係的全部接口和父類都保存了,這樣顯得更直觀。
在此,我保留了 _id 屬性,這是由於一般在 Java EE 環境中,DAO 第一次不是把整個對象都返回到表現層,而是隻返回了「標題」、「發佈時間」這些信息(並隱式的返回id),接着 DAO 與數據庫斷開;要查看詳情(好比文章內容)就須要進行 findById 操做,這時 DAO 要再次與數據庫交互,只有惟一標識符才能正確地找到對象。這種懶加載方式也是不少書籍所推薦的。
回到本文的範例程序中,這個 _id 屬性可由人工編碼實現的「序列」進行賦值,固然 db4o 也提供了內部標識符 Internal IDs,如圖 2 中的 id=1669;以及 UUIDs。
圖2. 對象結構
img src=」http://www.ibm.com/i/v14/rules/blue_rule.gif」 alt=」" width=」100%」 height=」1″ />
查詢數據庫
和 RDBMS 同樣,db4o 也有本身的查詢語言,分別是 QBE(Query by Example)、NQ(Native Queries)、SODA(Simple Object Database Access),db4o 更推薦使用 NQ 進行查詢。NQ 方式提供了很是強大的查詢功能,支持原生語言,也就意味着你可使用 Java 來判斷該對象是否符合條件,這是其餘數據庫查詢語言沒法比擬的。在某些狀況下, db4o 核心會將 NQ 翻譯成 SODA 以得到更高的性能。下面詳細介紹一下這三種查詢語言。
QBE(Query by Example)
QBE 規範可在這裏下載。QBE 最初由 IBM 提出,同時業界也有許多和 QBE 兼容的接口,包括著名的 Paradox。有些系統,好比微軟的 Access,它的基於表單的查詢也是受到了部分 QBE 思想的啓發。在 db4o 中,用戶可借用 QBE 快速上手,能夠很容易適應 db4o 存取數據的方式。
當利用 QBE 爲 db4o 提供模板(example)對象時,db4o 將返回全部和非默認值字段匹配的所有對象。內部是經過反射全部的字段和構造查詢表達式(全部非默認值字段結合」AND」表達式)來實現。
例如,利用 QBE 查找到車牌號爲「川A00000」的車主姓名,這是一個級聯查詢。清單4:
清單4
-
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
)
{
-
//打開數據庫
-
ObjectContainer db = Db4o.
openFile
(
"auto.yap"
);
-
try
{
-
//構造模板對象
-
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
{
-
//關閉鏈接
-
db.
close
(
);
-
}
-
}
-
}
可是 QBE 也有明顯的限制:db4o 必須反射模板(example)對象的全部成員;沒法執行更進一步的查詢表達式(例如 AND、OR、NOT 等等);不能約束 0(整型)、」」(空字符串)或者 null(對象),由於這些都被認爲是不受約束的。要繞過這些限制,db4o 提供了 NQ(Native Queries)。
SODA(Simple Object Database Access)
SODA ,簡單對象數據庫訪問,請查看官方站點,其中一位主要維護者是 Carl Rosenberger,Carl 正是 db4o 首席架構師。
SODA 就是一種與數據庫通信的對象 API。最終的目標是實現類型安全、對象複用、最小的字符串使用、與編程語言無關等特性。SODA 是 db4o 最底層的查詢 API,目前 SODA 中使用字符串來定義字段,這樣將不能實現類型安全也沒法在編譯時檢查代碼,並且寫起來較麻煩,固然要達到設計目標這個階段是必須的。大部分狀況下 NQ(Native Queries)是很好的查詢接口,不過遇到動態生成查詢的時候 SODA 就大有做爲了。
經過 SODA 查找到車牌號爲「川A00000」的車主姓名。清單5:
-
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
)
{
-
//打開數據庫
-
ObjectContainer db = Db4o.
openFile
(
"auto.yap"
);
-
try
{
-
//構造查詢對象
-
Query query=db.
query
(
);
-
//設置被約束實例
-
query.
constrain
(AutoInfo.
class
);
-
//設置被約束實例的字段和約束條件
-
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
{
-
//關閉鏈接
-
db.
close
(
);
-
}
-
}
-
}
經過 API,發現 Query 實例增長了 sortBy 按字段排序方法和 orderAscending正序、orderDescending 倒序排列方法,SODA 比 QBE 更進了一步。
NQ(Native Queries)
精彩老是在最後出場,NQ 纔是 db4o 查詢方式中最精彩的地方!有沒有想過用你熟悉的的編程語言進行數據庫查詢呢?要是這樣,你的查詢代碼將是 100% 的類型安全、100% 的編譯時檢查以及 100% 的可重構,很奇妙吧?NQ 能夠作到這些。
有兩篇論文專門講解了 NQ 的基本概念和設計思路,分別是 《Cook/Rosenberger,持久對象原生數據庫查詢語言》 和 《Cook/Rai,Safe Query Objects: Statically Typed Objects as Remotely Executable Queries》。做爲結果集的一部分,NQ 表達式必須返回 true 值來標記特定實例。若是可能的話 db4o 將嘗試優化 NQ 表達式,並依賴索引來運行表達式。
經過 NQ 查找到車牌號爲「川A00000」的車主姓名。清單6:
清單6
-
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
)
{
-
//打開數據庫
-
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
{
-
//關閉鏈接
-
db.
close
(
);
-
}
-
}
-
}
必須指出 NQ 的一個的問題是:在內部,db4o 設法把 NQ 轉換成 SODA。但並非全部的查詢表達式均可以成功轉換。有些查詢表達式的流向圖(flowgraph)很是難於分析。這種狀況下,db4o 將不得不實例化一些持久對象來真實地運行 NQ 表達式。
正在開發中的 NQ 查詢優化器就能夠化解這個障礙,它將分析 NQ 表達式的每一個部分,以確保最少許的實例化對象,以此提升性能。固然,優化器的不是靈丹妙藥,關鍵還須要本身多優化代碼。
開發 Java EE 項目常常會用到分頁,怎樣用 NQ 實現呢?向數據庫寫入六條記錄。清單7:
清單7
-
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
)
{
-
//打開數據庫
-
ObjectContainer db = Db4o.
openFile
(
"auto.yap"
);
-
try
{
-
List<AutoInfo> list = db.
query
(
new Predicate<AutoInfo>
(
)
{
-
public
boolean match
(AutoInfo ai
)
{
-
return
true;
-
}
-
}
);
-
//記錄總數
-
-
//每頁兩條,分三頁
-
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
{
-
//關閉鏈接
-
db.
close
(
);
-
}
-
}
-
}
咱們發現,在進行 NQ 查詢時並無加入任何條件(無條件返回 true),是否是至關於遍歷了整個數據庫?db4o 的設計者早就想到了這個問題,當 db.query() 執行完畢返回 list 實例的時候,db4o 只是與數據庫同步取出內部 IDs 而已,並無把全部的 AutoInfo 對象所有取出,只有在 list.get(x*2).getLicensePlate() 以後纔會去根據 IDs 取出記錄。因此沒必要擔憂性能問題。
結論
db4o 爲開發者提供了多種查詢方式,這些方式都很靈活。要引發你們注意的是:靈活在帶來便利的同時也對開發者自身素質提出了更高的要求,(好比排序,既能夠用 SODA 也能夠用 Java 集合對象實現)在開發過程當中必定要造成某種統一的開發模式,這樣 db4o 才能最高效能地爲我所用。
參考資料
學習
得到產品和技術