持久對(duì)象原生數(shù)據(jù)庫查詢語言(中文版)——預(yù)覽
持久對(duì)象原生數(shù)據(jù)庫查詢語言
設(shè)計(jì)白皮書
William R. Cook Carl Rosenberger
Department of Computer Sciences db4objects Inc.
The
wcook@cs.utexas.edu carl@db4o.com
大部分 Java 和 .NET 持 久架構(gòu)提供的接口在執(zhí)行查詢時(shí)必須以架構(gòu)特定的查詢語言書寫。這些接口是基于字符串的:查詢語句被定義在字符串中,并通過持久引擎進(jìn)行解釋。基于字符串的 查詢接口對(duì)程序員的生產(chǎn)力有相當(dāng)大的負(fù)面影響。對(duì)于像編譯時(shí)的類型檢查、自動(dòng)對(duì)齊、重構(gòu),這些開發(fā)環(huán)境特性,查詢語言是不可用的。程序員必須用兩種語言開 展工作:程序?qū)崿F(xiàn)語言和數(shù)據(jù)庫查詢語言。本文介紹原生數(shù)據(jù)庫查詢語言,以簡練且類型安全的方式直接使用 Java 和 C# 方法表達(dá)查詢。探討了原生數(shù)據(jù)庫查詢語言設(shè)計(jì)并提供了概括性的實(shí)現(xiàn)和優(yōu)化方面的議題。同時(shí),本文也探討了目前原生數(shù)據(jù)庫查詢語言設(shè)計(jì)的優(yōu)勢(shì)和劣勢(shì)。
1 介紹
當(dāng)今的對(duì)象數(shù)據(jù)庫和對(duì)象關(guān)系映射(ORM)工具在對(duì)象持久化做出了巨大的成就,讓開發(fā)者能很自然的進(jìn)行對(duì)象持久化,而在面向?qū)ο蟪绦蛑械牟樵冋Z言看起來有些不協(xié)調(diào)。這些查詢語言用單一的字符串表達(dá),或利用對(duì)象視圖把分散的字符串組合起來。讓我們看一小段例子。
本文中所有例子,我們都使用下面的類:
// Java
public class Student {
private String name;
private int age;
public String getName(){
return name;
}
public int getAge(){
return age;
}
}
// C#
public class Student {
private string name;
private int age;
public string Name {
get { return name; }
}
public int Age {
get{ return age; }
}
}
怎樣利用現(xiàn)有的對(duì)象查詢語言或 API 找到“年齡小于 20 歲的所有學(xué)生”?
OQL [8, 1]
String oql =
"select * from student in AllStudents where student.age < 20";
OQLQuery query = new OQLQuery(oql);
Object students = query.execute();
JDOQL [7, 9]
Query query =
persistenceManager.newQuery(Student.class, "age < 20");
Collection students = (Collection)query.execute();
db4o SODA, 使用 C# [4]
Query query = database.Query();
query.Constrain(typeof(Student));
query.Descend("age").Constrain(20).Smaller();
IList students = query.Execute();
上面的方法都存在一些普遍問題:
l 現(xiàn)代集成開發(fā)環(huán)境(IDEs)不會(huì)檢查內(nèi)嵌字符串的語義和語法錯(cuò)誤。在上面所有查詢語句中,age 字段和數(shù)值 20 都被認(rèn)為是數(shù)字類型,但是沒有一個(gè) IDE 或編譯器能檢查其實(shí)際正確性。如果開發(fā)者混淆了查詢代碼-―比如,改變了 age 字段的名字或類型,將導(dǎo)致――上面所有的查詢語句在運(yùn)行時(shí)報(bào)錯(cuò),而不會(huì)在編譯時(shí)提示。
l 由于現(xiàn)代 IDEs 不能重構(gòu)出現(xiàn)在字符串中的字段名,重構(gòu)行為將導(dǎo)致類模型和查詢字符串不同步。假設(shè)由于公司采取的編碼規(guī)則不同 Student 類的字段名 age 變成了 _age。現(xiàn)在,已有 age 查詢語句都報(bào)錯(cuò)了,我們只好手工修改。
l 現(xiàn)代敏捷開發(fā)技術(shù)鼓勵(lì)不斷進(jìn)行重構(gòu)來維持清晰和與時(shí)俱進(jìn)的類模型,以便準(zhǔn)確重現(xiàn)不斷演進(jìn)的域模型。如果查詢代碼難于維護(hù),它會(huì)延遲決定重構(gòu)的時(shí)間并不可避免的引入低質(zhì)量代碼。
l 所有列出的查詢操作都私有的實(shí)現(xiàn) Student 類
student.age
而不是使用它的公共接口
student.getAge() / student.Age
因此他們都破壞了面向?qū)ο蠓庋b規(guī)則,違反接口和實(shí)現(xiàn)應(yīng)該分離的面向?qū)ο蠓▌t。
l 開發(fā)者經(jīng)常需要在實(shí)現(xiàn)語言和查詢語言間切換上下文。查詢語句無法使用已有的程序?qū)崿F(xiàn)語言代碼。
l 沒有明確支持創(chuàng)建可復(fù)用的查詢組件。一次復(fù)雜查詢由查詢字符串連接而成,但是程序語言的復(fù)用特性(方法調(diào)用、多態(tài)、重載)卻沒有一樣能夠用來改善易用性。傳遞參數(shù)到基于字符串的查詢顯得有些笨拙且容易出錯(cuò)。
l 嵌入的字符串可能受到注入攻擊的威脅。
2 設(shè)計(jì)目標(biāo)
我們能否用普通 Java 或 C# 語言來表達(dá)同樣的查詢呢?
// Java
student.getAge() < 20
// C#
student.Age < 20
開發(fā)者直接書寫查詢語句而不必考慮特定的查詢語言或 API。IDE 可以及時(shí)幫助我們減少書寫錯(cuò)誤。查詢語句將是完全類型安全的而且很容易獲取 IDE 的重構(gòu)特性。查詢語句也是原生的、可測試的,運(yùn)行時(shí)依靠內(nèi)存里的集合類而不是后臺(tái)數(shù)據(jù)庫。
咋看起來,這種方法似乎不適合數(shù)據(jù)庫查詢機(jī)制。由于所有的候選對(duì)象都不得不從數(shù)據(jù)庫中實(shí)例化,原生執(zhí)行 Java/C# 代碼依賴于完全包含某個(gè)類的所有存儲(chǔ)對(duì)象,這會(huì)導(dǎo)致巨大的性能開銷。這個(gè)問題的解決方案在Cook 和 Rai[3] 出版的“Safe Query Objects”中:通過翻譯成下面的持久化系統(tǒng)查詢語言或 API(SQL[6]、OQL[1]、JDOQL[9]、EJBQL[11]、SODA[10]、等等),Java/C# 查詢表達(dá)式的源代碼或字節(jié)碼能被分析和優(yōu)化,另外還要利用索引和其他數(shù)據(jù)庫優(yōu)化手段。本文中,我們精煉了早期關(guān)于安全查詢對(duì)象的理念,更簡明和自然的定義了原生查詢語言。我們將用 Java 和 .NET 語言的最新優(yōu)勢(shì)特性來進(jìn)行綜合查詢檢測,包括匿名類和委派。
因此,我們的原生查詢語言目標(biāo)是:
100% 的原生 查詢語言應(yīng)能用實(shí)現(xiàn)語言(Java 或 C#)完全表達(dá),并完全遵循實(shí)現(xiàn)語言的語義。
100% 的面向?qū)ο?/span> 查詢語言應(yīng)可運(yùn)行在自己的實(shí)現(xiàn)語言中,允許未經(jīng)優(yōu)化執(zhí)行普通集合而不用自定義預(yù)處理。
100% 的類型安全 查詢語言應(yīng)能完全獲取現(xiàn)代 IDE 的特性,比如語法檢測、類型檢測、重構(gòu),等等。
優(yōu)化 為實(shí)現(xiàn)性能優(yōu)化,它應(yīng)該能把原生查詢語言翻譯成持久體系的查詢語言或 API。能在編譯期或載入期對(duì)源代碼或字節(jié)碼進(jìn)行分析和翻譯。
3 定義原生數(shù)據(jù)庫查詢語言 API
原生查詢語言是什么樣的呢?為了構(gòu)建最小化設(shè)計(jì),通過一次一個(gè)的增加設(shè)計(jì)特性來演進(jìn)一個(gè)簡單查詢。并將使用 Java 和 C#(.NET 2.0)作為實(shí)現(xiàn)語言。
讓我們從本文前面設(shè)計(jì)的類開始。假設(shè)想要查詢“所有小于 20 歲且名字中包含‘f’的學(xué)生”。
1. 主要的查詢表達(dá)式可由編程語言簡單表述:
// Java
student.getAge() < 20 && student.getName().contains("f")
// C#
student.Age < 20 && student.Name.Contains("f")
2. 我們需要有傳遞 Student 對(duì)象到表達(dá)式的途徑,并返回結(jié)果到查詢處理器。可以定義 student 參數(shù)以及表達(dá)式返回一個(gè)布爾值達(dá)到目的:
// 偽 Java 代碼
(Student student){
return student.getAge() < 20
&& student.getName().contains("f");
}
// 偽 C# 代碼
(Student student){
return student.Age < 20
&& student.Name.Contains("f");
}
3. 現(xiàn)在我們必須包裝上述部分并植入進(jìn)對(duì)象,以便編程語言鑒定其合法性。現(xiàn)在可以傳遞對(duì)象到數(shù)據(jù)庫引擎、集合、或其他查詢處理器了。在 .NET 2.0 中,我們只需用委派。在 Java 中,需要一個(gè)命名方法,也就是類對(duì)象由方法傳遞。為實(shí)現(xiàn)這些要求,我們?yōu)榉椒ㄟx擇名字,也就是類的名字。沿用這個(gè)范例,為 .NET 2.0 設(shè)置集合過濾。最后,類名是“Predicate”,方法名是“match”。
// Java
new Predicate(){
public boolean match(Student student){
return student.getAge() < 20
&& student.getName().contains("f");
}
}
// C#
delegate(Student student){
return student.Age < 20
&& student.Name.Contains("f");
}
4. 對(duì)于 .NET 2.0,我們已做好了最簡單的查詢接口設(shè)計(jì)。上面是一個(gè)合法對(duì)象。對(duì)于 Java,查詢規(guī)范應(yīng)該是標(biāo)準(zhǔn)的,為查詢語句設(shè)計(jì)抽象基類: Predicate 類。
// Java
public abstract class Predicate <ExtentType> {
public <ExtentType> Predicate (){}
public abstract boolean match (ExtentType candidate);
}
遵循泛型約定,我們還得稍微修改,為 Java 查詢對(duì)象增加擴(kuò)展類型。
new Predicate <Student> () {
public boolean match(Student student){
return student.getAge() < 20
&& student.getName().contains("f");
}
}
5. 盡管上面已完成概念性的東西,我們還是要提供完整的例子來詮釋 API。尤其要說明依賴數(shù)據(jù)庫的查詢是怎樣的,以便和本文中給出的基于字符串的范例做對(duì)比。
// Java
List <Student> students = database.query <Student> (
new Predicate <Student> () {
public boolean match(Student student){
return student.getAge() < 20
&& student.getName().contains("f");
}
});
// C#
IList <Student> students = database.Query <Student> (
delegate(Student student){
return student.Age < 20
&& student.Name.Contains("f");
});
上述例子闡述了核心概念。通過 Java 中匿名類和 .NET 中委托的支持,我們精確的反映了 Cook/Rai 關(guān)于安全查詢[3]的概念。表現(xiàn)出來的是更加簡練和直接的查詢描述。
API 逐步增加所有的必要部分,可以讓我們?cè)?/span> Java 和 C# 中找出最自然高效的表達(dá)查詢的方式。額外的,像參數(shù)化和動(dòng)態(tài)查詢,可用相似的手法包含進(jìn)原生數(shù)據(jù)庫查詢語言中。
到這里,我們已克服現(xiàn)有的基于字符串的查詢語言的缺點(diǎn)并提高了生產(chǎn)力、健壯性、可維護(hù)性和性能。
4 詳細(xì)規(guī)范
只有在實(shí)踐體驗(yàn)之后,一份最終和詳盡的原生數(shù)據(jù)庫查詢規(guī)范才能成文。因此本章節(jié)只在做些臆斷。我們應(yīng)該指出我們所了解到的精華以及原生查詢手段所遇到的問題和解決辦法。
4.1 優(yōu)化
單獨(dú)談 API,原生查詢不是第一個(gè)。如果沒有優(yōu)化,我們只能提供“最簡單的靠單一方法運(yùn)行一個(gè)類的所有實(shí)例并返回布爾值的概念”。著名接口:Smalltalk 80 包含基于 predicate[5, 2] 從集合中選擇條目的方法。
優(yōu)化是原生查詢中的關(guān)鍵組建。用戶書寫原生查詢表達(dá)式,數(shù)據(jù)庫端以同等性能的基于字符串的查詢語句執(zhí)行,這些我們?cè)谇懊娴慕榻B中討論過。
盡
管原生查詢的核心概念很簡單,提供高效解決方案的工作很重要。生成的查詢表達(dá)式代碼必須被分析和轉(zhuǎn)換成等價(jià)的數(shù)據(jù)庫查詢語言格式。不必在原生查詢時(shí)轉(zhuǎn)換所
有代碼。如果優(yōu)化器不能處理查詢表達(dá)式中部分或全部代碼,這些代碼最終會(huì)回退到實(shí)際對(duì)象的實(shí)例中,并直接運(yùn)行查詢表達(dá)式代碼, 或部分代碼, 真實(shí)對(duì)象在查詢后回到中間值。由于這一過程可能很慢,它將有助于為開發(fā)者提供在開發(fā)時(shí)提供反饋。這種反饋可能包含優(yōu)化器怎樣“理解”查詢表達(dá)式,為表達(dá)式創(chuàng)建潛在優(yōu)化計(jì)劃。這將有助于開發(fā)者參照最佳優(yōu)化語法調(diào)整他們的開發(fā)風(fēng)格,讓開發(fā)者提供關(guān)于改善優(yōu)化的反饋。
優(yōu)
化實(shí)際上是怎樣工作的呢?在編譯或載入期,增強(qiáng)器(一個(gè)分離應(yīng)用程序,或是編譯器或載入器的“插件”)將檢測所有原生查詢表達(dá)式的源代碼或字節(jié)碼,并在數(shù)
據(jù)庫引擎生成高效格式的附加代碼。在運(yùn)行時(shí),被替代的代碼將被替換成之前的查詢表達(dá)式。當(dāng)增加優(yōu)化器來編輯、構(gòu)建或者兩者都是,這種機(jī)制對(duì)開發(fā)者是透明
的。
我們的伙伴表示疑問,是否可得到滿意的優(yōu)化。由于原生查詢格式和數(shù)據(jù)庫格式都定義好了,因此開發(fā)優(yōu)化器是一個(gè)長期的任務(wù),但是我們非常樂觀的估計(jì)會(huì)取得好成績的。首先 Cook/Rai [3] 構(gòu)建了 JDO 映射實(shí)現(xiàn),這一點(diǎn)非常令人鼓舞。db4objects 今天[4]已經(jīng)構(gòu)建首個(gè)未優(yōu)化的 db4o 原生查詢預(yù)覽產(chǎn)品,并計(jì)劃在2005年11月構(gòu)建原生查詢優(yōu)化產(chǎn)品 5.0 版本。
4.2 約束
理想情況下,任何代碼都可以出現(xiàn)在查詢表達(dá)式中。通過實(shí)踐,約束是要保證一個(gè)穩(wěn)定的環(huán)境,并安裝在一個(gè)有資源限制的環(huán)境中。我們建議:
變量 查詢表達(dá)式中的變量聲明應(yīng)該是合法的。
對(duì)象創(chuàng)建 臨時(shí)對(duì)象本質(zhì)上是為復(fù)雜查詢準(zhǔn)備的,所以它們的構(gòu)建應(yīng)該被查詢表達(dá)式所支持。
靜態(tài)調(diào)用 靜態(tài)調(diào)用是 OO 語言概念的一部分,所以它們也應(yīng)是合法的。
少量界面 查詢表達(dá)式宗旨是快速。它們不應(yīng)該受 GUI 的影響。
線程 查詢表達(dá)式將被大量觸發(fā)。因此不允許創(chuàng)建線程。
安全約束 由于查詢表達(dá)式實(shí)際上可能以服務(wù)器上的真實(shí)對(duì)象執(zhí)行,因此它們應(yīng)被約束在一定范圍內(nèi)。它可以允許或禁止某些表空間/包中的方法執(zhí)行以及對(duì)象創(chuàng)建。
只讀 不能在運(yùn)行中的查詢代碼上修改持久對(duì)象。這種限制保證了結(jié)果復(fù)用以及規(guī)范之外的事務(wù)性保證。
超時(shí) 考慮到對(duì)資源使用的限制,數(shù)據(jù)庫引擎會(huì)選擇讓長時(shí)間運(yùn)行的查詢超時(shí)。超時(shí)的配置不是原生查詢規(guī)范的一部分,但還是推薦實(shí)現(xiàn)它。
內(nèi)存限制 內(nèi)存限制可設(shè)計(jì)為超時(shí)。一個(gè)可配置的內(nèi)存上限可限制每個(gè)查詢表達(dá)式,推薦實(shí)現(xiàn)這一特性。
未定義的行為 如果規(guī)范沒有明確的限制,那么所有的構(gòu)造函數(shù)都是允許的。
4.3 異常
查詢表達(dá)式發(fā)生任何錯(cuò)誤,流程都應(yīng)該繼續(xù)執(zhí)行。拋出未捕獲異常的查詢表達(dá)式應(yīng)該被處理成只返回false(查詢失敗)。同時(shí)還要有讓開發(fā)者能發(fā)現(xiàn)、跟蹤異常的機(jī)制。我們建議最終的實(shí)現(xiàn)應(yīng)該支持異常回調(diào)和異常日志機(jī)制。
4.4 排序
返回排序?qū)ο笠矐?yīng)由原生代碼定義。給出一個(gè)精確的定義超出了本文的范圍,但是可以簡單列舉一下。使用 Java Comparator:
// Java
List <Student> students = database.query <Student> (
new Predicate <Student> () {
public boolean match(Student student){
return student.getAge() < 20 && student.getName().contains("f");
}
});
Collections.sort(students, new Comparator <Student>(){
public int compare(Student student1, Student student2) {
return student1.getAge() - student2.getAge();
}
});
上述代碼應(yīng)該可運(yùn)行在有和沒有優(yōu)化處理器的環(huán)境中。數(shù)據(jù)庫服務(wù)器上,通過調(diào)用數(shù)據(jù)庫引擎的排序函數(shù),查詢和排序都可被優(yōu)化為一步完成。
5 結(jié)論
有充分的理由把原生查詢語言作為一種主流標(biāo)準(zhǔn)。正如我們講到的,原生查詢語言克服了基于字符串 API 的缺點(diǎn)。只有在實(shí)踐之后才能完全體會(huì)到原生查詢語言的潛力。優(yōu)勢(shì)如下:
強(qiáng)大 查詢語言可利用標(biāo)準(zhǔn)的面向?qū)ο缶幊碳夹g(shù)。
生產(chǎn)率 原生數(shù)據(jù)庫查詢語言享有先進(jìn)開發(fā)手段的優(yōu)點(diǎn),包括,靜態(tài)類型檢測,重構(gòu)以及自動(dòng)對(duì)齊。
標(biāo)準(zhǔn) 由于編程語言規(guī)范已定義好標(biāo)準(zhǔn),原生數(shù)據(jù)庫查詢語言可 100% 保證跨不同數(shù)據(jù)庫實(shí)現(xiàn)的兼容性。
效率 原生數(shù)據(jù)庫查詢語言可自動(dòng)編譯成傳統(tǒng)查詢語言或 APIs,以便能支持已有的高性能數(shù)據(jù)庫引擎。
簡單 正如前面提到的,原生數(shù)據(jù)庫查詢語言 API 只能是一個(gè)類一個(gè)方法。因此,原生數(shù)據(jù)庫查詢語言非常易學(xué),也很容易定義。應(yīng)該把它們作為 JSR 提交給 JCP(Java Community Process)。
感謝
首先要感謝 Johan Strandler 在 TheServerSide 提交的文章,使得兩個(gè)作者能一起合作,Patrick Romer 起草了本文的首個(gè)草案,Rodrigo B. de Oliveira 貢獻(xiàn)了 .NET 委派語法,Klaus Wuestefeld 建議了“原生數(shù)據(jù)庫查詢語言”術(shù)語,Roberto Zicari、Rick Grehan 以及 Dave Orme 校對(duì)了本文草案,以上所有參與者都為本文有爭議部分達(dá)成共識(shí)而努力。
參考文獻(xiàn)
[1] R. G. G. Cattell, D. K. Barry, M. Berler, J. Eastman, D.
C. Russell, O. Schadow, T. Stanienda, and F. Velez, editors. The Object
Data Standard ODMG 3.0. Morgan Kaufmann, January 2000.
[2] W. Cook. Interfaces and specifications for the Smalltalk collection
classes. In OOPSLA, 1992.
[3] W. R. Cook and S. Rai. Safe query objects: statically typed objects
as remotely executable queries. In G.-C. Roman, W. G. Griswold, and
B. Nuseibeh, editors, Proceedings of the 27th International Conference
on Software Engineering (ICSE), pages 97{106. ACM, 2005.
[4] db4objects web site. http://www.db4o.com.
[5] A. Goldberg and D. Robson. Smalltalk-80: the Language and Its Im-
plementation. Addison-Wesley, 1983.
[6] ISO/IEC. Information technology - database languages - SQL - part
3: Call-level interface (SQL/CLI). Technical Report 9075-3:2003,
ISO/IEC, 2003.
[7] JDO web site. http://java.sun.com/products/jdo.
[8] ODMG web site. http://www.odmg.org.
[9] C. Russell. Java Data Objects (JDO) Specification JSR-12. Sun Mi-
crosystems, 2003.
[10] SODA - Simple Object Database Access.
http://sourceforge.net/projects/sodaquery .
[11] Sun Microsystems. Enterprise Java Beans Specification, version 2.1.
2002. http://java.sun.com/j2ee/docs.html.
請(qǐng)注意!引用、轉(zhuǎn)貼本文應(yīng)注明原譯者:Rosen Jiang 以及出處:http://www.aygfsteel.com/rosen
posted on 2006-02-27 12:32 Vincent.Chen 閱讀(208) 評(píng)論(0) 編輯 收藏 所屬分類: Database