持久對象原生數據庫查詢語言 設計白皮書

持久對象原生數據庫查詢語言
設計白皮書
 
 
 
 
William R. Cook                                Carl Rosenberger
Department of Computer Sciences     db4objects Inc.
The University of Texas at Austin        1900 South Norfolk Street
Austin , TX 78712-0233 , U.S.A.          San Mateo , CA , 94403 , U.S.A.
[email protected]                       [email protected]
 
                                               2005 年 8 月 23 日
 
 摘要
大部分 Java 和 .NET 持久架構提供的接口在執行查詢時必須以架構特定的查詢語言書寫。這些接口是基於字符串的:查詢語句被定義在字符串中,並通過持久引擎進行解釋。基於字符串的查詢接口對程序員的生產力有相當大的負面影響。對於像編譯時的類型檢查、自動對齊、重構,這些開發環境特性,查詢語言是不可用的。程序員必須用兩種語言開展工作:程序實現語言和數據庫查詢語言。本文介紹原生數據庫查詢語言,以簡練且類型安全的方式直接使用 Java 和 C# 方法表達查詢。探討了原生數據庫查詢語言設計並提供了概括性的實現和優化方面的議題。同時,本文也探討了目前原生數據庫查詢語言設計的優勢和劣勢。
 
 
1 介紹
 
當今的對象數據庫和對象關係映射( ORM )工具在對象持久化做出了巨大的成就,讓開發者能很自然的進行對象持久化,而在面向對象程序中的查詢語言看起來有些不協調。這些查詢語言用單一的字符串表達,或利用對象視圖把分散的字符串組合起來。讓我們看一小段例子。
 
    本文中所有例子,我們都使用下面的類:
 
// 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; }
}
}
 
          怎樣利用現有的對象查詢語言或 API 找到“年齡小於 20 歲的所有學生”?
 
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         現代集成開發環境( IDEs )不會檢查內嵌字符串的語義和語法錯誤。在上面所有查詢語句中, age 字段和數值 20 都被認爲是數字類型,但是沒有一個 IDE 或編譯器能檢查其實際正確性。如果開發者混淆了查詢代碼-―比如,改變了 age 字段的名字或類型,將導致――上面所有的查詢語句在運行時報錯,而不會在編譯時提示。
 
l         由於現代 IDEs 不能重構出現在字符串中的字段名,重構行爲將導致類模型和查詢字符串不同步。假設由於公司採取的編碼規則不同 Student 類的字段名 age 變成了 _age 。現在,已有 age 查詢語句都報錯了,我們只好手工修改。
 
l         現代敏捷開發技術鼓勵不斷進行重構來維持清晰和與時俱進的類模型,以便準確重現不斷演進的域模型。如果查詢代碼難於維護,它會延遲決定重構的時間並不可避免的引入低質量代碼。
 
l         所有列出的查詢操作都私有的實現 Student 類
 
student.age
 
而不是使用它的公共接口
 
       student.getAge() / student.Age
 
因此他們都破壞了面向對象封裝規則,違反接口和實現應該分離的面向對象法則。
 
l         開發者經常需要在實現語言和查詢語言間切換上下文。查詢語句無法使用已有的程序實現語言代碼。
 
l         沒有明確支持創建可複用的查詢組件。一次複雜查詢由查詢字符串連接而成,但是程序語言的複用特性(方法調用、多態、重載)卻沒有一樣能夠用來改善易用性。傳遞參數到基於字符串的查詢顯得有些笨拙且容易出錯。
 
l         嵌入的字符串可能受到注入攻擊的威脅。
 
 
設計目標
      
我們能否用普通 Java 或 C# 語言來表達同樣的查詢呢?
 
// Java
student.getAge() < 20
 
// C#
student.Age < 20
 
開發者直接書寫查詢語句而不必考慮特定的查詢語言或 API 。 IDE 可以及時幫助我們減少書寫錯誤。查詢語句將是完全類型安全的而且很容易獲取 IDE 的重構特性。查詢語句也是原生的、可測試的,運行時依靠內存裏的集合類而不是後臺數據庫。
 
    咋看起來,這種方法似乎不適合數據庫查詢機制。由於所有的候選對象都不得不從數據庫中實例化,原生執行 Java/C# 代碼依賴於完全包含某個類的所有存儲對象,這會導致巨大的性能開銷。這個問題的解決方案在 Cook 和 Rai[3] 出版的“ Safe Query Objects ”中:通過翻譯成下面的持久化系統查詢語言或 API ( SQL[6] 、 OQL[1] 、 JDOQL[9] 、 EJBQL[11] 、 SODA[10] 、等等), Java/C# 查詢表達式的源代碼或字節碼能被分析和優化,另外還要利用索引和其他數據庫優化手段。本文中,我們精煉了早期關於安全查詢對象的理念,更簡明和自然的定義了原生查詢語言。我們將用 Java 和 .NET 語言的最新優勢特性來進行綜合查詢檢測,包括匿名類和委派。
    因此,我們的原生查詢語言目標是:
 
100% 的原生 查詢語言應能用實現語言( Java 或 C# )完全表達,並完全遵循實現語言的語義。
 
100% 的面向對象 查詢語言應可運行在自己的實現語言中,允許未經優化執行普通集合而不用自定義預處理。
 
100% 的類型安全 查詢語言應能完全獲取現代 IDE 的特性,比如語法檢測、類型檢測、重構,等等。
 
優化 爲實現性能優化,它應該能把原生查詢語言翻譯成持久體系的查詢語言或 API 。能在編譯期或載入期對源代碼或字節碼進行分析和翻譯。
 
 
定義原生數據庫查詢語言 API
 
原生查詢語言是什麼樣的呢?爲了構建最小化設計,通過一次一個的增加設計特性來演進一個簡單查詢。並將使用 Java 和 C# ( .NET 2.0 )作爲實現語言。
 
       讓我們從本文前面設計的類開始。假設想要查詢“所有小於 20 歲且名字中包含‘ f ’的學生”。
 
1.         主要的查詢表達式可由編程語言簡單表述:
 
// Java
student.getAge() < 20 && student.getName().contains("f")
 
// C#
student.Age < 20 && student.Name.Contains("f")
 
2.         我們需要有傳遞 Student 對象到表達式的途徑,並返回結果到查詢處理器。可以定義 student 參數以及表達式返回一個布爾值達到目的:
 
// 僞 Java 代碼
(Student student){
 return student.getAge() < 20
      && student.getName().contains("f");
}
 
// 僞 C# 代碼
(Student student){
 return student.Age < 20
      && student.Name.Contains("f");
}
 
3.         現在我們必須包裝上述部分並植入進對象,以便編程語言鑑定其合法性。現在可以傳遞對象到數據庫引擎、集合、或其他查詢處理器了。在 .NET 2.0 中,我們只需用委派。在 Java 中,需要一個命名方法,也就是類對象由方法傳遞。爲實現這些要求,我們爲方法選擇名字,也就是類的名字。沿用這個範例,爲 .NET 2.0 設置集合過濾。最後,類名是“ 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.         對於 .NET 2.0 ,我們已做好了最簡單的查詢接口設計。上面是一個合法對象。對於 Java ,查詢規範應該是標準的,爲查詢語句設計抽象基類 : Predicate 類。
 
// Java
public abstract class Predicate <ExtentType> {
public <ExtentType> Predicate (){}
public abstract boolean match (ExtentType candidate);
}
 
遵循泛型約定,我們還得稍微修改,爲 Java 查詢對象增加擴展類型。
 
new Predicate <Student> () {
public boolean match(Student student){
return student.getAge() < 20
&& student.getName().contains("f");
}
}
 
5.         儘管上面已完成概念性的東西,我們還是要提供完整的例子來詮釋 API 。尤其要說明依賴數據庫的查詢是怎樣的,以便和本文中給出的基於字符串的範例做對比。
 
// 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 關於安全查詢 [3] 的概念。表現出來的是更加簡練和直接的查詢描述。
 
API 逐步增加所有的必要部分,可以讓我們在 Java 和 C# 中找出最自然高效的表達查詢的方式。額外的,像參數化和動態查詢,可用相似的手法包含進原生數據庫查詢語言中。
 
到這裏,我們已克服現有的基於字符串的查詢語言的缺點並提高了生產力、健壯性、可維護性和性能。
 
4          詳細規範
 
只有在實踐體驗之後,一份最終和詳盡的原生數據庫查詢規範才能成文。因此本章節只在做些臆斷。我們應該指出我們所瞭解到的精華以及原生查詢手段所遇到的問題和解決辦法。
 
4.1 優化
 
單獨談 API ,原生查詢不是第一個。如果沒有優化,我們只能提供“最簡單的靠單一方法運行一個類的所有實例並返回布爾值的概念”。著名接口: Smalltalk 80 包含基於 predicate[5, 2] 從集合中選擇條目的方法。
 
優化是原生查詢中的關鍵組建。用戶書寫原生查詢表達式,數據庫端以同等性能的基於字符串的查詢語句執行,這些我們在前面的介紹中討論過。
 
儘管原生查詢的核心概念很簡單,提供高效解決方案的工作很重要。生成的查詢表達式代碼必須被分析和轉換成等價的數據庫查詢語言格式。不必在原生查詢時轉換所有代碼。如果優化器不能處理查詢表達式中部分或全部代碼,這些代碼最終會回退到實際對象的實例中,並直接運行查詢表達式代碼 , 或部分代碼 , 真實對象在查詢後回到中間值。由於這一過程可能很慢,它將有助於爲開發者提供在開發時提供反饋。這種反饋可能包含優化器怎樣“理解”查詢表達式,爲表達式創建潛在優化計劃。這將有助於開發者參照最佳優化語法調整他們的開發風格,讓開發者提供關於改善優化的反饋。
 
優化實際上是怎樣工作的呢?在編譯或載入期,增強器(一個分離應用程序,或是編譯器或載入器的“插件”)將檢測所有原生查詢表達式的源代碼或字節碼,並在數據庫引擎生成高效格式的附加代碼。在運行時,被替代的代碼將被替換成之前的查詢表達式。當增加優化器來編輯、構建或者兩者都是,這種機制對開發者是透明的。
 
我們的夥伴表示疑問,是否可得到滿意的優化。由於原生查詢格式和數據庫格式都定義好了,因此開發優化器是一個長期的任務,但是我們非常樂觀的估計會取得好成績的。首先 Cook/Rai [3] 構建了 JDO 映射實現,這一點非常令人鼓舞。 db4objects 今天 [4] 已經構建首個未優化的 db4o 原生查詢預覽產品,並計劃在 2005 年 11 月構建原生查詢優化產品 5.0 版本。
 
4.2 約束
 
理想情況下,任何代碼都可以出現在查詢表達式中。通過實踐,約束是要保證一個穩定的環境,並安裝在一個有資源限制的環境中。我們建議:
 
變量 查詢表達式中的變量聲明應該是合法的。
 
對象創建 臨時對象本質上是爲複雜查詢準備的,所以它們的構建應該被查詢表達式所支持。
 
靜態調用 靜態調用是 OO 語言概念的一部分,所以它們也應是合法的。
 
少量界面 查詢表達式宗旨是快速。它們不應該受 GUI 的影響。
 
線程 查詢表達式將被大量觸發。因此不允許創建線程。
 
安全約束 由於查詢表達式實際上可能以服務器上的真實對象執行,因此它們應被約束在一定範圍內。它可以允許或禁止某些表空間 / 包中的方法執行以及對象創建。
 
只讀 不能在運行中的查詢代碼上修改持久對象。這種限制保證了結果複用以及規範之外的事務性保證。
 
超時 考慮到對資源使用的限制,數據庫引擎會選擇讓長時間運行的查詢超時。超時的配置不是原生查詢規範的一部分,但還是推薦實現它。
 
內存限制 內存限制可設計爲超時。一個可配置的內存上限可限制每個查詢表達式,推薦實現這一特性。
 
未定義的行爲 如果規範沒有明確的限制,那麼所有的構造函數都是允許的。
 
4.3 異常
 
查詢表達式發生任何錯誤,流程都應該繼續執行。拋出未捕獲異常的查詢表達式應該被處理成只返回 false (查詢失敗)。同時還要有讓開發者能發現、跟蹤異常的機制。我們建議最終的實現應該支持異常回調和異常日誌機制。
 
4.4 排序
 
返回排序對象也應由原生代碼定義。給出一個精確的定義超出了本文的範圍,但是可以簡單列舉一下。使用 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();
 }
});
 
上述代碼應該可運行在有和沒有優化處理器的環境中。數據庫服務器上,通過調用數據庫引擎的排序函數,查詢和排序都可被優化爲一步完成。
 
 
5 結論
 
有充分的理由把原生查詢語言作爲一種主流標準。正如我們講到的,原生查詢語言克服了基於字符串 API 的缺點。只有在實踐之後才能完全體會到原生查詢語言的潛力。優勢如下:
 
強大 查詢語言可利用標準的面向對象編程技術。
 
生產率 原生數據庫查詢語言享有先進開發手段的優點,包括,靜態類型檢測,重構以及自動對齊。
 
標準 由於編程語言規範已定義好標準,原生數據庫查詢語言可 100% 保證跨不同數據庫實現的兼容性。
 
效率 原生數據庫查詢語言可自動編譯成傳統查詢語言或 APIs ,以便能支持已有的高性能數據庫引擎。
 
簡單 正如前面提到的,原生數據庫查詢語言 API 只能是一個類一個方法。因此,原生數據庫查詢語言非常易學,也很容易定義。應該把它們作爲 JSR 提交給 JCP(Java Community Process) 。
 
感謝
 
首先要感謝 Johan Strandler TheServerSide 提交的文章,使得兩個作者能一起合作, Patrick Romer 起草了本文的首個草案, Rodrigo B. de Oliveira 貢獻了 .NET 委派語法, Klaus Wuestefeld 建議了“原生數據庫查詢語言”術語, Roberto Zicari Rick Grehan 以及 Dave Orme 校對了本文草案,以上所有參與者都爲本文有爭議部分達成共識而努力。
 
參考文獻
 
[1] R. G. G. Cattell, D. K. Barry, M. Berler, J. Eastman, D. Jordan ,
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 .

---------------------------------------------------------------------------------------------------------------------------
本文中文版已由 DB4O 官方發佈,並邀請 DB4O 香港 Team 對譯文進行檢查和修改。下載地址:
http://www.db4o.com/china/Native%20Queries%20Whitepaper(Chinese).pdf
 
 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章