深入db4o
這是Rick Grehan發(fā)表在TheServerSide上的一篇關(guān)于面向?qū)ο髷?shù)據(jù)庫--db4o的文章,較全面地介紹了db4o的關(guān)鍵特性,希望對(duì)大家認(rèn)識(shí)db4o能有所幫助。(2007.12.07最后更新)db4o-針對(duì)對(duì)象的數(shù)據(jù)庫-是一個(gè)完全的對(duì)象數(shù)據(jù)庫;它以使對(duì)象在其生命周期中-無論是在數(shù)據(jù)庫內(nèi)或是在外-都保持著它們的本性這樣一種方式操縱對(duì)象。不論類的復(fù)雜性如何,對(duì)象的內(nèi)容,結(jié)構(gòu)和關(guān)系都能夠被保存。
更準(zhǔn)確地說,db4o是一個(gè)數(shù)據(jù)庫引擎,你只要將它的一個(gè)jar文件包含到你的數(shù)據(jù)庫應(yīng)用的類路徑中就可以使用它了(至少對(duì)于Java是這樣的)。所以,db4o運(yùn)行在與你的應(yīng)用程序相同的進(jìn)程空間中,并能被直接地調(diào)用;它不需要類似于在ODBC或JDBC中使用的驅(qū)動(dòng)文件。db4o存在針對(duì)Java,.NET和Mono的版本;它們?cè)诠δ苌隙急舜讼嗟取?事實(shí)上,使用.NET創(chuàng)建的db4o數(shù)據(jù)庫也能由Java程序訪問;反之亦然。)
db4o是開源的。可執(zhí)行文件,源代碼和文檔可從http://www.db4objects.com/中下載。廣泛的例子程序,和一個(gè)活躍的用戶社區(qū)一樣,也都可以從這個(gè)站點(diǎn)中找到。
db4o最引人的特性之一就是它在簡易性與強(qiáng)大的功能之間的顯著平衡。一方面,它的API是如此地易于掌握和方便使用,即使是初學(xué)者也能在相同的時(shí)間內(nèi)創(chuàng)建一個(gè)功能完備的數(shù)據(jù)庫對(duì)象。另一方面,這些相同的API也提供了更底層的能夠深入調(diào)用數(shù)據(jù)庫引擎的方法,以允許核心開發(fā)者為了得到適當(dāng)?shù)男阅芏苌钊氲皆撘娴膬?nèi)部中去調(diào)整db4o的工具。
db4o的特性就是最好的證明--這勝過只是討論--所以我們將通過示例這種方法去證明db4o。然而,我們必須牢記本文通篇只是展示了db4o特性中的一部分罷了。感興趣的朋友會(huì)發(fā)現(xiàn)為了知曉該數(shù)據(jù)庫引擎的全部功能而去查閱db4o的文檔所花的時(shí)間是值得的。
db4o基礎(chǔ)
讓我們以初學(xué)者使用db4o時(shí)可能會(huì)做的事情開始:定義了一些類,然后持久化這些類的對(duì)象。我們所假定的類為同樣也是假定的QA項(xiàng)目做一個(gè)跟蹤測試的系統(tǒng)。我們的系統(tǒng)由兩個(gè)類組成,首先是TestSuite類:
public class TestSuite {
private String name; // Test Suite name
private String description;
private String configuration;
private char overallScore;
private ArrayList <TestCase> cases;
private long dateExec;
... <remainder of TestSuite definition> ...
}
TestSuite是TestCase對(duì)象的容器,(一個(gè)測試用例就是一個(gè)可單獨(dú)執(zhí)行的測試程序--相關(guān)的測試用例組合成一個(gè)測試組)。測試組使用額外的,全局的數(shù)據(jù)成員,每個(gè)數(shù)據(jù)成員的用途也是相對(duì)明顯的:configuration記錄被測試的指定系統(tǒng);overallScore是對(duì)整個(gè)測試組一個(gè)簡要的評(píng)分('P'代表通過,'F'代表失敗,'B'代表被阻塞,等等。);dateExec是一個(gè)毫秒級(jí)的域,標(biāo)識(shí)該測試組被執(zhí)行時(shí)的日期與時(shí)刻。是ArrayList對(duì)象的cases含有單個(gè)的測試用例,由TestCase類定義:
public class TestCase {
private String name;
private String comment;
private char status;
private long duration;
private float result;
... <remainder of TestCase definition> ...
}
每個(gè)測試用例都有一個(gè)名稱,形式自由的注釋字段,狀態(tài)(通過或失敗),持續(xù)時(shí)間和結(jié)果(例如,為了與測試-字節(jié)/秒的吞吐量-的任意數(shù)據(jù)關(guān)聯(lián))。
因?yàn)槲覀兯P(guān)注是db4o的使用,所以我們不想在描述這些類的使用細(xì)節(jié)上停留。就讓我們簡單地說,我們已經(jīng)執(zhí)行了一個(gè)特別地測試組中所有的測試用例,將測試的結(jié)果存放在一個(gè)TestSuite對(duì)象中(與TestCase對(duì)象相關(guān)的ArrayList對(duì)象cases中),然后關(guān)閉數(shù)據(jù)庫。是不是太容易了。
// Create the database
new File("testsuites.YAP").delete();
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Store the TestSuite object
db.set(testsuite);
// Close the database
db.close();
就是它。彈指一揮間,你就已經(jīng)做到了。(當(dāng)然,為了保持簡潔,我們?nèi)サ袅藙?chuàng)建TestSuite對(duì)象和它的TestCase組件的細(xì)節(jié))
停下來想想上述代碼做了什么事情。特別要考慮你沒有看到的--db4o已經(jīng)做了但還未被告之的事情。
首先,我們不需要告訴db4o任何關(guān)于TestSuite類的結(jié)構(gòu)的信息;不需要我們的幫助,db4o就能發(fā)現(xiàn)這個(gè)結(jié)構(gòu)。利用Java反射機(jī)制的能力,db4o測定TestSuite類的結(jié)構(gòu),并勾勒出該類的裝配方式以推導(dǎo)出此類對(duì)象的成員與關(guān)鍵數(shù)據(jù)。
第二,我們不必建議db4o去關(guān)注ArrayList。不僅我們不必將ArrayList的大小告訴db4o,而且我們也不必把它里面的內(nèi)容告訴db4o。正如db4o在它剛接觸testsuite對(duì)象時(shí)就能夠發(fā)現(xiàn)它所需要的一切,db4o也能知道它所需要的關(guān)于(在ArrayList中的)TestCase對(duì)象的所有信息。
結(jié)果就是,如果我們把testsuite作為一個(gè)任意寵大而復(fù)雜的對(duì)象樹的根,db4o能找到并存儲(chǔ)整個(gè)樹而不需要我們的任何協(xié)助。所以,存儲(chǔ)這個(gè)處于根部的對(duì)象testsuite也就是存儲(chǔ)了整個(gè)ArrayList對(duì)象。
最后,我們也沒有必須要求db4o以事務(wù)的保護(hù)性方式去調(diào)用set方法。任何會(huì)修改ObjectContainer(表示數(shù)據(jù)庫的db4o對(duì)象)的調(diào)用都會(huì)自動(dòng)地開啟一個(gè)事務(wù),除非已經(jīng)有一個(gè)活躍的事務(wù)了。此外還會(huì)調(diào)用close方法去終止這個(gè)事務(wù),所以上述代碼等價(jià)于:
db.startTransaction();
db.set(testsuite);
db.commitTransaction();
db.close();
此處的startTransaction和commitTransaction方法是為了證明我們的觀點(diǎn)而虛構(gòu)的。db4o也確實(shí)有顯示地提交或中止事務(wù)的方法,但為了使原先的代碼足夠的簡潔我們沒有使用這些方法。db4o隱式的事務(wù)使得數(shù)據(jù)庫能夠一直處于一致的狀態(tài);一旦commit方法已經(jīng)執(zhí)行了,數(shù)據(jù)庫的完整性就能夠得到保證,甚至是發(fā)生了災(zāi)難性失敗也能如此。
查詢I - QBE
有了已經(jīng)存于數(shù)據(jù)庫中的對(duì)象,下一步我們想要展示的操作將肯定就是查詢和恢復(fù)。db4o提供了三種查詢API:有一種簡單,有一種優(yōu)雅,還有一種則復(fù)雜。每一種API都有其所長,并適用于不同的查詢條件。以db4o的眼光來看,你選擇哪一種API并沒有關(guān)系:它們都是可兼容的。
我們以簡單的API開始:query by exampel(QBE)。
使用QBE是如此的容易:為你的查詢目標(biāo)構(gòu)建一個(gè)'模板'對(duì)象,然后把它傳入ObjectContainer的query方法。實(shí)際上,你就是告訴db4o'去拿到所有與這個(gè)對(duì)象看起來一樣的對(duì)象'。(這與JavaSpaces查詢API非常相似;為了清楚地了解如何處理基本數(shù)據(jù)類型,可以看下面的內(nèi)容,db4o的處理方式與JavaSpaces不同。也要注意,JavaSpace Entry對(duì)象期望使用public字段,db4o則沒有這種要求。)
假設(shè)一個(gè)測試組名為"Network Throughput",我們想取出這個(gè)測試組執(zhí)行的所有測試以便我們能確定失敗了的測試所占的百分比(基于TestSuite的overalScore值)。使用QBE,完成該工作的代碼如下:
// Open the database
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Instantiate the template object, filling in
// only the name field
testTemplate = new TestSuite("Network Throughput");
// Execute the query
ObjectSet result = db.get(testTemplate);
fails = 0.0f;
total = 0.0f;
// Step through results,
while(result.hasNext())
{
testsuite = (TestSuite)result.next();
if(testsuite.getOverallScore()=='F')
fails += 1.0f;
total += 1.0f;
}
if(total == 0.0f)
System.out.println("No tests of that type found.");
else
{
System.out.println("Percent failed: " + (fails/total * 100.0f) + "%");
System.out.println("Total executed: " + total);
}
db.close();
在上面的代碼中,testTemplate是QBE的模板對(duì)象。注意,只有它的name字段有真實(shí)的值;所有其它的成員變量不是為null就是為0。Null或0字段不參與QBE查詢;因此,調(diào)用db.get方法就會(huì)返回在該數(shù)據(jù)庫中name字段匹配"Network Throughput"的所有TestSuite對(duì)象。匹配的TestSuite對(duì)象將返回在一個(gè)ObjectSet結(jié)果對(duì)象中。上述代碼遍歷該結(jié)果,取出對(duì)象,然后計(jì)算結(jié)果并展示出來。
QBE明顯的優(yōu)點(diǎn)就是它的簡易性。不需要掌握其它單獨(dú)的查詢語言。另外,QBE也是類型安全的:你不需要?jiǎng)?chuàng)建一個(gè)類似于SQL的查詢語句
SELECT TestSuite.overallScore FROM TestSuite WHERE TestSuite.name = 200.0
另一方面,由于該查詢是由Java代碼創(chuàng)建的,編譯器不會(huì)允許你把一個(gè)浮點(diǎn)值賦給一個(gè)String字段;反之亦然。
QBE明顯的缺點(diǎn)就是它只能執(zhí)行"等于"查詢。另外,QBE使用null值去確定不參與查詢的String或?qū)ο笠贸蓡T變量,使用0值去指定不參與查詢的數(shù)字字段。所以,例如,我不能發(fā)明一個(gè)QBE查詢?nèi)カ@得所有result字段的值為0的TestCase對(duì)象。
更為精細(xì)的查詢要求一個(gè)能力更強(qiáng)的查詢機(jī)制,而db4o恰恰就有這樣一種機(jī)制。
查詢方式II - 原生查詢(Native Query)
db4o的原生查詢系統(tǒng)應(yīng)該是能想像得到的最具彈性的查詢機(jī)制。不像使用數(shù)據(jù)庫查詢語言去構(gòu)建查詢,你是使用"無格式的普通Java語句"去構(gòu)造原生查詢。原生查詢用兩種手段去實(shí)現(xiàn)這件不可思意的工作:一個(gè)是Predicate類;另一個(gè)是QueryComparator接口。這個(gè)類包含一個(gè)可重載的(overrideable)回調(diào)方法,該方法將指定如何從數(shù)據(jù)庫中選擇對(duì)象(如果你愿意,你將會(huì)看到查詢語句的主體....)。這個(gè)接口只聲明了一個(gè)方法,該方法用于指明如何對(duì)查詢結(jié)果進(jìn)行排序。
作為一個(gè)例子,我們假設(shè)想找到在給定的一周內(nèi)執(zhí)行過了的總得分為"failed",但與之關(guān)聯(lián)的測試用例中有超過一半的被評(píng)為"passed"的測試組。這不是一個(gè)簡單的"等于"查詢,所以它不能使用QBE構(gòu)建。
然而,db4o的原生查詢可以直接地生成該查詢。首先,我們繼承db4o的Predicate類:
// Predicate class sub-class for native query example
public class NativeQueryQuery extends Predicate<TestSuite>
{
ObjectContainer db;
private long startdate;
private long enddate;
// 構(gòu)造器要在本地獲得ObjectContainer對(duì)象和日期范圍
public NativeQueryQuery(ObjectContainer _db,
long _start, long _end)
{
db = _db;
startdate = _start;
enddate = _end;
}
// 這就是查詢的主體
public boolean match(TestSuite testsuite)
{
float passed;
float total;
TestCase testcase;
// 判斷testsuite是否在指定的日期范圍內(nèi)
if(testsuite.getDateExec()<startdate ||
testsuite.getDateExec()>enddate) return false;
// 如果該測試組對(duì)象中沒有測試用例對(duì)象,則拒絕將該測試組對(duì)象放入查詢結(jié)果中
if(testsuite.getNumberOfCases()==0)
return false;
// 檢查該測試組對(duì)象中的測試用例的通過率是否超過50%
passed = 0.0f;
total = 0.0f;
for(int i=0; i<testsuite.getNumberOfCases(); i++)
{
testcase = testsuite.getTestCase(i);
if(testcase.getStatus()=='P')
passed+=1.0f;
total+=1.0f;
}
if((passed/total)<.5) return false;
return true;
}
}
注意在這個(gè)類的使用中使用了Java泛型語義,這就是告訴db4o只去取TestSuite對(duì)象。當(dāng)查詢執(zhí)行時(shí),TestSuite對(duì)象就會(huì)傳入match方法(我們之前提到過的回調(diào)方法),如果傳入的TestSuite對(duì)象符合查詢規(guī)范該方法就返回true,否則就返回false。
match方法中的代碼首先確定侯選對(duì)象是否是在一周的日期范圍內(nèi)。如果是,則循環(huán)該對(duì)象中的成員變量測試用例的對(duì)象,計(jì)算出所有通過了的測試用例的總數(shù)。如果,得到的通過率小于50%,該測試組就被拒絕;否則,就讓它通過。
我們可以使用如下的代碼準(zhǔn)確地展示該查詢程序:
. . .
TestSuite testsuite;
NativeQueryQuery nqqClass;
Date now;
// Open the database
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Instantiate a NativeQueryQuery object,
// setting the start and end dates for
// any test in the past week
// 604800000 = milliseconds in a week
now = new Date();
nqqClass = new NativeQueryQuery(db,
now.getTime()-604800000L,
now.getTime());
// Execute the query and display the
// results
System.out.println("Results:");
ObjectSet results = db.query(nqqClass);
if(results.isEmpty())
System.out.println(" NOTHING TO DISPLAY");
while(results.hasNext())
{
testsuite = (TestSuite)(results.next());
System.out.println(testsuite.toString());
}
db.close();
. . .
可以把原生查詢想像成這樣:目標(biāo)類的對(duì)象一個(gè)接一個(gè)從數(shù)據(jù)庫中取出,然后把它們傳入match方法中。只有那些被match方法返回true的對(duì)象才會(huì)置于查詢結(jié)果ObjectSet對(duì)象中。基本上可以說,如果你會(huì)知道如何寫Java代碼,那么你就知道如何寫原生查詢。
那么排序呢?如果想按日期的升序排列查詢結(jié)果,我們就要實(shí)現(xiàn)QueryComparator接口,如下所示:
public class NativeQuerySort implements QueryComparator<TestSuite>{
public int compare(TestSuite t1, TestSuite t2)
{
if (t1.getDateExec() < t2.getDateExec()) return -1;
if (t1.getDateExec() > t2.getDateExec()) return 1;
return 0;
}
}
compare方法的作用十分明顯。那些在查詢中得以勝出的對(duì)象會(huì)成對(duì)的傳入compare方法,如果第一個(gè)對(duì)象會(huì)排在第二個(gè)對(duì)象之前,相同或之后的位置,該方法就會(huì)分別返回一個(gè)小于,等于或大于0的值。為了準(zhǔn)確地說明對(duì)查詢結(jié)果的排序,我們實(shí)例化NativeQuerySort,并把對(duì)query方法的調(diào)用修改成如下:
. . .
// Instantiate the sort class
nqsClass = new NativeQuerySort();
. . .
ObjectSet results = db.query(nqqClass, nqsClass);
. . .
其它的代碼仍然與原先的保持一致。
好懷疑的讀者可能會(huì)抱怨道,原生查詢只是一種編程上的小伎倆--相比較于直接去拿到所有的TestSuite對(duì)象然后再排除其中不符合條件的對(duì)象這樣的程序,原生查詢并不快。
是的,但并不十分準(zhǔn)確。原生能夠被優(yōu)化。你所需要做的只是把兩個(gè)jar文件--db4o-xxx-nqopt.jar(xxx表示db4o的版本)和bloat.jar--置于CLASSPATH環(huán)境變量中。在查詢執(zhí)行的時(shí)候,這些類庫中的代碼會(huì)對(duì)(在match方法中)例如基本數(shù)據(jù)類型比較,算術(shù)和布爾表達(dá)式,簡單的對(duì)象成員訪問,以及更多方面的結(jié)構(gòu)進(jìn)行優(yōu)化。這個(gè)被支持的優(yōu)化的列表在不停的增長,因?yàn)閐b4o引擎還在擴(kuò)展優(yōu)化的范圍。
查詢方式III - S.O.D.A.
db4o獨(dú)一無二的能力之一就是它的API被分層了。開發(fā)者能夠選擇通過高層次--賦予數(shù)據(jù)庫引擎相當(dāng)大的自由度,讓它決定如何去實(shí)現(xiàn)它的操作--或者開發(fā)者也可以使用一種更直接地方式去訪問db4o。后一種選擇為程序員平添了更多的負(fù)擔(dān),程序員必須更加小心地引導(dǎo)數(shù)據(jù)庫引擎的內(nèi)部工作。但回報(bào)就是得到一個(gè)更快,能力更強(qiáng)的數(shù)據(jù)庫應(yīng)用。
db4o的S.O.D.A.(Simple Object Data Access)查詢機(jī)制就是該層次API的一個(gè)完美的例子。S.O.D.A.就是db4o的內(nèi)部查詢系統(tǒng)--QBE和原生查詢都被翻譯成了S.O.D.A.。然而,應(yīng)用程序也能直接地調(diào)用S.O.D.A.。
假設(shè)我們想找到所有名為"Network Throughput",且至少擁有一個(gè)其result字段--我們使用這個(gè)參數(shù)作為字節(jié)/秒的量度--不小于指定值(比方說,100)的測試用例的測試組。為該請(qǐng)求而做的S.O.D.A.查詢可能就像這樣:
. . .
TestSuite testsuite;
// Open the database
ObjectContainer db = Db4o.openFile("testsuites.YAP");
// Construct the query
Query query = db.query();
query.constrain(TestSuite.class);
Constraint nameConst = query.descend("name").
constrain("Network Throughput");
query.descend("cases").descend("result").
constrain(100.0f).smaller().and(nameConst);
System.out.println("Results:");
// Execute the query
ObjectSet result = query.execute();
if(result.isEmpty())
System.out.println("NOTHING TO DISPLAY");
while(result.hasNext())
{
testsuite = (TestSuite)(result.next());
System.out.println(testsuite.toString());
}
db.close();
. . .
在由Illustration 1所示圖表的幫助下,這些有點(diǎn)兒神秘的代碼就變得不那么神秘了。該程序所構(gòu)建的歸總起來就是一個(gè)用于指導(dǎo)底層數(shù)據(jù)庫引擎的查詢圖(Query Graph)。descend方法創(chuàng)建了該圖的一個(gè)分支,該分支向下步入對(duì)象的結(jié)構(gòu)中。每個(gè)descend方法就在這個(gè)樹中構(gòu)建一個(gè)結(jié)點(diǎn),可以在這些結(jié)點(diǎn)上再附上一個(gè)約束(使用constrain方法)。用SQL的話來說,約束指定了查詢的"WHERE"子句部分。多個(gè)約束可以在與(and)或或(or)方法的協(xié)助下結(jié)合起來。在上面的查詢中我們已經(jīng)使用了and方法去關(guān)聯(lián)這些約束。

與其它的查詢方式一樣,查詢結(jié)果返回到ObjectSet對(duì)象中,通過遍歷該對(duì)象就可取出那些拿到的對(duì)象。
注意,由于S.O.D.A.是一種低層次的訪問方法,沒有智能的指示,它就沒有默認(rèn)的行為。訪問cases對(duì)象的成員變量result字段的代碼很簡單
query.descend("cases").descend("result"). ...
我們并沒有告訴S.O.D.A."cases"是一個(gè)集合對(duì)象。所以當(dāng)查詢執(zhí)行時(shí),它會(huì)不被察覺地檢測ArrayList對(duì)象cases中所有元素(TestCase對(duì)象)的result字段,然后會(huì)正確地返回那些擁有符合搜索規(guī)范的測試用例的測試組。
db4o性能調(diào)優(yōu)
我們已經(jīng)展示了db4o的基本操作(但無關(guān)緊要的刪除操作除外,下面將會(huì)提到它)。但,正如我們?cè)诒疚耐ㄆ岬降模琩b4o發(fā)布(expose)了一個(gè)API層次結(jié)構(gòu),以允許開發(fā)者能夠選擇以何種層次的API去控制建立在該數(shù)據(jù)庫引擎之上的應(yīng)用程序。從另一方面看,如果你所想做的只是向數(shù)據(jù)庫存入,及從數(shù)據(jù)庫取出對(duì)象,那么你就已經(jīng)看到了你所需要的一切。然而,如果你的應(yīng)用的需求超出了添加,更新,查詢和刪除,可能還有一個(gè)db4o的特性可解決你的問題。
db4o的ObjectContainer實(shí)際上發(fā)布(expose)了兩個(gè)API。第一個(gè)API非常的簡單,由十個(gè)方法組成。這些方法處理數(shù)據(jù)庫的打開與關(guān)閉;添加,更新,查詢和刪除對(duì)象;及提交或中止事務(wù)。短言之,該API為你提供了在操縱數(shù)據(jù)庫時(shí)所需要所有功能。然而,該API中的一個(gè)方法--ext()--是進(jìn)入"被擴(kuò)展的"ObjectContainer的一個(gè)入口。該被擴(kuò)展的ObjectContainer為深入控制db4o的內(nèi)部發(fā)布(expose)了更多方法。例如,你可以獲得并改變數(shù)據(jù)庫的配置上下文,使用它你能夠修改該引擎的行為。
例如,假設(shè)你已經(jīng)從數(shù)據(jù)庫中拿到了一個(gè)TestSuite對(duì)象,發(fā)現(xiàn)該對(duì)象中的數(shù)據(jù)是錯(cuò)誤的,并決定該對(duì)象應(yīng)該被刪除。此外,你還決定你必須刪除的不僅是該TestSuite對(duì)象,而且還有所有與之關(guān)聯(lián)的TestCase對(duì)象(在ArrayList對(duì)象cases中)。
你是可以冗長而乏味地遍歷這個(gè)ArrayList對(duì)象,一個(gè)接一個(gè)地刪除每一個(gè)TestCase對(duì)象,然后再刪除TestSuite對(duì)象本身。可能一種更好的解決方案是為這個(gè)TestSuite對(duì)象啟用db4o的"級(jí)聯(lián)刪除"特性。
. . .
// Fetch the database's configuration context
Configuration config = db.ext().configure();
// Get the ObjectClass for TestSuite
ObjectClass oc = config.objectClass("testsuites.TestSuite");
// Turn on cascaded delete
oc.cascadeOnDelete(true);
... ...
db.delete(ts1);
. . .
在上述代碼中,我們實(shí)例化了一個(gè)ObjectClass對(duì)象,該對(duì)象使我們能夠訪問到TestSuite對(duì)象的db4o內(nèi)部表現(xiàn)形式。我們打開cascadeOnDelete標(biāo)記,以便當(dāng)執(zhí)行db4o.delete(ts1)時(shí),不僅ts1對(duì)象會(huì)被刪除,而且所有由ts1引用的TestCase對(duì)象也會(huì)被刪除。(由于顯而易見的原因,默認(rèn)情況下,級(jí)聯(lián)刪除是被關(guān)閉的)
作為另一個(gè)例子,假設(shè)你想為數(shù)據(jù)庫預(yù)分配存儲(chǔ)空間,以至于要最小化磁盤驅(qū)動(dòng)器的頭移動(dòng)(head movement)。(最好是在硬盤碎片整理之后,并新創(chuàng)建一個(gè)數(shù)據(jù)庫時(shí)這樣做。)并且假設(shè)以O(shè)bjectContainer對(duì)象db作為打開了的數(shù)據(jù)庫:
// Fetch the database's configuration context
// 獲得數(shù)據(jù)庫的配置上下文
Configuration config = db.ext().configure();
// Pre-allocate 200,000 bytes
// 預(yù)分配200000000字節(jié)
config.reserveStorageSpace(200000000L);
把數(shù)據(jù)庫文件預(yù)擴(kuò)展到200000000字節(jié)(略小于200兆字節(jié))。假設(shè)該磁盤已經(jīng)做碎片整理了,那么被分配的塊就是連續(xù)的,這可顯著提升數(shù)據(jù)庫的訪問。
db4o高級(jí)應(yīng)用
完全可以說,db4o在它不大的空間(約500K)內(nèi)已裝入了足夠多的特性,相比較于db4o在運(yùn)行過程中所做的眾多事情,我們不能花費(fèi)更多的筆墨去解釋它們了。但是有兩個(gè)特性十分突出,以至于肯定要提到它們。
db4o的對(duì)象復(fù)制實(shí)現(xiàn)了被總稱為面向?qū)ο蟀娴臄?shù)據(jù)庫同步。使用復(fù)制所提供的功能,你能為一個(gè)數(shù)據(jù)庫中的一個(gè)對(duì)象做一個(gè)副本,并將該副本放入另一個(gè)數(shù)據(jù)庫中。使用這樣的方法,副本對(duì)象就無形中和原始對(duì)象關(guān)聯(lián)在了一起。對(duì)任一對(duì)象--原始對(duì)象或副本對(duì)象--的改變都會(huì)被跟蹤到,以便在之后的某個(gè)時(shí)候,數(shù)據(jù)庫能夠被重組,并且這兩個(gè)數(shù)據(jù)庫中對(duì)象的不同之處可被分解(例如,可同步這兩個(gè)數(shù)據(jù)庫)。
它工作起來就像這樣:為使一個(gè)數(shù)據(jù)庫可被復(fù)制,與事務(wù)計(jì)數(shù)器一樣,該數(shù)據(jù)庫中被創(chuàng)建的任何一個(gè)對(duì)象都用一個(gè)唯一全局標(biāo)識(shí)符(UUID)進(jìn)行了標(biāo)記。當(dāng)你從原始數(shù)據(jù)庫中"復(fù)制"一個(gè)對(duì)象到另一個(gè)數(shù)據(jù)庫中,副本對(duì)象會(huì)帶著與它的原始對(duì)象相同的UUID和事務(wù)計(jì)數(shù)器。副本數(shù)據(jù)庫現(xiàn)在就可以從它的原始數(shù)據(jù)庫那兒弄走了。修改副本對(duì)象的內(nèi)容將會(huì)導(dǎo)致對(duì)象的事務(wù)計(jì)數(shù)器被修改。所以,當(dāng)這兩個(gè)數(shù)據(jù)庫重新連接起來,db4o內(nèi)建的同步處理機(jī)制就能一個(gè)對(duì)象一個(gè)對(duì)象地進(jìn)行正確的匹配(使用UUID),并確定原始或副本對(duì)象是否已經(jīng)改變了。db4o甚至能追蹤到每個(gè)對(duì)象發(fā)生最后一次修改時(shí)的時(shí)間,以便用戶寫的沖突解決代碼能確定哪個(gè)對(duì)象是最近更新的。
從操作行為來看,db4o的同步處理機(jī)制與原生查詢十分相似。回想一下,當(dāng)實(shí)現(xiàn)了一個(gè)原生查詢類時(shí),我們要定義一個(gè)match方法,該方法確定哪些對(duì)象符合(或不符合)查詢規(guī)范。使用同步復(fù)制,我們要定義一個(gè)ReplicationProcess對(duì)象,我們會(huì)把沖突處理對(duì)象傳入該方法中。這些Java代碼可能像這樣:
. . .
ReplicationProcess replication = db1.ext().
replicationBegin(db2, new ReplicationConflictHandler()
{
public Object resolveConflict(
ReplicationProcess rprocess, Object a, Object b)
{
. . . ...
return winning_object;
}
)
};
在上述代碼中,Object a是來自于數(shù)據(jù)庫db1的對(duì)象,Object b則來自于數(shù)據(jù)庫db2。默認(rèn)情況下,同步是雙向的。同步處理保證勝出的對(duì)象(由resolveConflict方法返回的對(duì)象)能存入這兩個(gè)數(shù)據(jù)庫中。所以當(dāng)復(fù)制完成時(shí),這兩個(gè)數(shù)據(jù)庫中被復(fù)制的對(duì)象就同步了。
最后,db4o最強(qiáng)大的特性之一就是它能毫不費(fèi)力地容忍類結(jié)構(gòu)的演變。假設(shè),在向數(shù)據(jù)庫內(nèi)加入數(shù)百個(gè)TestSuite對(duì)象之后,我們決定這個(gè)類必須要被修改。就是說,我們已經(jīng)被告之,該系統(tǒng)必須能追蹤到每個(gè)TestSuite的執(zhí)行QA工程師,所以必須加入如下字段
private int engineerID;
到TestSuite類的定義中。
現(xiàn)在我們就遇到了兩個(gè)相關(guān)聯(lián)的問題。第一個(gè)問題不是很糟糕:已存在于數(shù)據(jù)庫中的用于表示測試的TestSuite對(duì)象,并沒有為它們記錄QA工程師的ID,所以我們將不得不將一個(gè)虛擬的值賦給這些對(duì)象的engineerID字段;該值會(huì)指出"未記錄QA工程師的ID"。第二個(gè)問題就更難應(yīng)付了:我們必須不知原因地把已有的TestSuite對(duì)象移植到"新的"類結(jié)構(gòu)中。我們必須為數(shù)據(jù)庫中所有已存在的TestSuite對(duì)象加上一個(gè)engineerID字段。除了把舊數(shù)據(jù)庫中的對(duì)象復(fù)制到一個(gè)中間性的文件,然后重新創(chuàng)建一個(gè)數(shù)據(jù)庫這種方法之外,我們還能怎么做呢?
幸運(yùn)地是,使用db4o,我們確實(shí)什么都不用做。為了能操縱新的engineerID字段,以完成業(yè)務(wù)邏輯上所要求的變化,如果就只是向(我們的應(yīng)用程序中的)TestSuite類添加一個(gè)engineerID字段,我們完全不必觸動(dòng)db4o API的任何調(diào)用。當(dāng)db4o使用"新的"TestSuite類結(jié)構(gòu)去讀取"舊的"TestSuite對(duì)象,db4o將認(rèn)為這些對(duì)象中的engineerID字段消失了,并且會(huì)優(yōu)雅地把該字段的值設(shè)為0。如果我們把0當(dāng)作"未記錄QA工程師的ID",那么我們所做的移植就完成了。寫入到數(shù)據(jù)庫中的新TestSuite對(duì)象將會(huì)包括新字段。(事實(shí)上,對(duì)舊的TestSuite對(duì)象本身進(jìn)行重寫,會(huì)使db4o不被察覺地為這些對(duì)象加上新字段。)所以,通過發(fā)布一個(gè)包含新的TestSuite定義的更新應(yīng)用,我們完全可以不被察覺地從舊的TestSuite對(duì)象移植到新的...正如前述應(yīng)用所做的那樣。
全方位數(shù)據(jù)庫
通過適當(dāng)?shù)貞?yīng)用,db4o就能成為數(shù)據(jù)庫中的"瑞士軍刀"。它占用足夠小的內(nèi)存空間,使它能夠被包含在一個(gè)不需要消耗大量資源的項(xiàng)目中。同樣地,一個(gè)數(shù)據(jù)庫只在磁盤上占用一個(gè)文件的事實(shí)可能會(huì)使人們?cè)诘谝谎劭吹剿鼤r(shí),不能認(rèn)識(shí)到它豐富的功能。將數(shù)據(jù)庫從一個(gè)地方移到另一個(gè)地方就是一個(gè)簡單的文件拷貝;你不必?fù)?dān)心分離的索引文件,數(shù)據(jù)文件,數(shù)據(jù)庫結(jié)構(gòu)文件等等這些文件的位置。對(duì)于快速部署和零管理的數(shù)據(jù)庫應(yīng)用,db4o很能勝任。
另外,根據(jù)我們已多次描述過的,db4o在簡單性和優(yōu)雅之間達(dá)到了適度的平衡。db4o的QBE既如此的簡單,又如此的功能強(qiáng)大,對(duì)于一組令人驚奇的應(yīng)用,它經(jīng)常是我們唯一需要的查詢API。如果你主要是通過指針導(dǎo)航而不是查詢?nèi)ピL問數(shù)據(jù)庫,QBE就特別有吸引力。在這種情況下,QBE經(jīng)常能高效地拿到一個(gè)對(duì)象網(wǎng)絡(luò)(Object Network)的根對(duì)象。然后,你就能使用db4o的激活(activation)功能從根部向下進(jìn)行對(duì)象引用導(dǎo)航,如果這些對(duì)象都完全在內(nèi)存中的話,你就更可以這么做了。
而在使用QBE并不高效的時(shí)候,原生查詢和S.O.D.A.就能派上用場了,并且它們伴隨著一堆特性和低層次的API。我們還沒有展示db4o的加密功能,插入式文件I/O(例如,它允許你添加"寫后讀(read-after-write)"驗(yàn)證),信號(hào)量,客戶端/服務(wù)器端模式,以及其它難以計(jì)數(shù)的功能。我們結(jié)論性的建議很簡單:當(dāng)你的下一個(gè)Java應(yīng)用需要一個(gè)數(shù)據(jù)庫,在最終開始編碼之前,你可以去訪問一下http://www.db4objects.com/。這是很值得的。