db4o学习笔记(三)、db4o查询详解
摘要:
在本单元中详细介绍了db4o数据库的几种查询方式,对于易于初学者使用的QBE存在的弱点及db4o开发者极力推荐的NQ查询方式在不同开发语言中的实现机制、特点、示例及执行效能作了相应的阐述,同时也对NQ本身的不足进行简要的介绍,本单元中提供了大量的示例在最后给出了所有示例完整的代码。
正文:
db4o数据库系统提供了三种查询接口:(1)、QBE,对于初接触db4o的朋友来说,QBE是一种十分简洁的查询方式,它通过向数据库提供一个模板,系统将会返回所有成员数据非默认值的对象,在上一单元中我们简要的对QBE进行了介绍;(2)、NQ,NQ则是db4o中主要的查询接口,也是开发者所推荐的方法;(3)、SODA API,SODA是一种底层的查询接口,它提供了向后兼容的能力并且对于动态生成查询十分有用,不像NQ采用的是强类型方式。
3.1、QBE
当使用QBE进行查询时我们需要提供一个模板对象,数据库将会返回所有成员数据为非默认值的对象。这将通过反射所有的成员数据并生成一个由"与(AND)关系"连接所有非默认值成员数据的查询表达式来完成查询操作,下面便是在上一单元中所使用的示例:
2Pilot proto = new Pilot("Michael Schumacher", 0);
3ObjectSet result = db.Get(proto);
4ListResult(result);
采用这种方式存在几个明细的局限性:
(1)、db4o需要反射你提供模板对象的所有成员数据;
(2)、不能使用高级查询表达式,如AND、OR、NOT等;
(3)、对于数据不能使用强制条件,如int的0,string的空字串或空引用类型,因为它们在查询时都解释为非强制关系;
(4)、需要为类提供非初始化成员数据的构造函数,这意味着在定义数据成员时不能对其进行初始化(对此笔者就曾犯过错误);
为此db4o提供了另外一种查询方式NQ。
3.2、NQ
使用NQ为你的查询提供了开发语言内置的支持能力(所谓NQ - 原生/本地化大概就是这个意思吧),提供类型安全机制、编译时检查及反射功能,使用面向对象的方法调用来完成查询。NQ是db4o数据库查询的主要接口也是开发者极力推荐的数据查询方式,因为NQ充分运用了开发语言的语义完整性,将会成为将来完美而安全的选择。
3.2.1、概念
关于NQ的概念最初来源于以下两篇文章,有兴趣的朋友可以参考一下:
Cook/Rosenberger, Native Queries for Persistent Objects, A Design White Paper
Cook/Rai, Safe Query Objects: Statically Typed Objects as Remotely Executable Queries
3.2.2、原则
NQ提供了运行于类型所有实例对象上单条或多条代码的能力,NQ查询表达式需要返回true以标记相应的对象实例为查询返回集合的一部分。如果可能的话db4o将会对NQ查询表达式进行优化并在索引的基础上执行而不是为查询提供示例对象。(此一句理解得不是很准确原文为db4o will attempt to optimize native query expressions and run them against indexes and without instantiating actual objects, where this is possible.)
3.2.3、示例
下面我们看看在某些开发语言中NQ是如何进行使用的。
C# .NET 2.0
2 return pilot.Points == 100;
3});
2 public boolean match(Pilot pilot) {
3 return pilot.getPoints() == 100;
4 }
5});
2 public boolean match(Pilot pilot) {
3 return pilot.getPoints() == 100;
4 }
5});
2
3public class PilotHundredPoints : Predicate {
4 public boolean Match(Pilot pilot) {
5 return pilot.Points == 100;
6 }
7}
从上述代码中可以看出,对于不支持泛型的语言来说,都需要提供一个扩展com.db4o.Predicate的类,并提供一个参数为待查询的类并返回布尔值的函数#.Match()或#.match(),其函数签名为:
使用NQ时可以充分利用现代开发环境(IDE)所提供的代码模板及自动完成功能,从而为你完成所有的代码输入功能,在VS2005中我们可以使用代码片断来创建这样的模板。
3.2.4、高级示例
对于复杂的查询任务,使用NQ的语法十分的精确和简捷,下面让我们看看其是如何查询指定姓名及积分范围的车手信息的对象,并与SODA查询进行简单的对比。
SODA查询:
2db.Set(new Pilot("Michael Schumacher", 100));
3db.Set(new Pilot("Rubens Barrichello", 99));
4
5
6[retrieveComplexSODA]
7Query query=db.Query();
8query.Constrain(typeof(Pilot));
9Query pointQuery=query.Descend("_points");
10query.Descend("_name").Constrain("Rubens Barrichello")
11 .Or(pointQuery.Constrain(99).Greater()
12 .And(pointQuery.Constrain(199).Smaller()));
13ObjectSet result=query.Execute();
14ListResult(result);
15
下面是NQ进行同样查询的语法,使用NQ将会拥有上面述及的所有优点:自动完成、反射、编译时检查等等。
C# .NET 2.0
2 return pilot.Points > 99
3 && pilot.Points < 199
4 || pilot.Name == "Rubens Barrichello";
5});
Java JDK 1.5
2 public boolean match(Pilot pilot) {
3 return pilot.getPoints() > 99
4 && pilot.getPoints() < 199
5 || pilot.getName().equals("Rubens Barrichello");
6 }
7});
3.2.5、任意代码
现在我们知道使用NQ进行查询是十分高效的,原则上可以使用任意的代码来作为查询表达式,而这也需要引起我们的注意,特别是对于那些可持久化的对象的影响。
还是通过一个对于多数语言都有效的示例来揭示其中的具体含义吧。
2namespace com.db4o.f1.chapter1
3{
4 public class ArbitraryQuery : Predicate
5 {
6 private int[] _points;
7
8 public ArbitraryQuery(int[] points)
9 {
10 _points=points;
11 }
12
13 public bool Match(Pilot pilot)
14 {
15 foreach (int points in _points)
16 {
17 if (pilot.Points == points)
18 {
19 return true;
20 }
21 }
22 return pilot.Name.StartsWith("Rubens");
23 }
24 }
25}
3.2.6、NQ执行性能
必须指出的是NQ也存在某些不足的地方:在db4o的底层总是试图将NQ查询转换为SODA,这对于所有的查询几乎是不可能完成的。对某些查询要分析其流程是十分困难的,为此db4o将不得不在持久化对象上执行真实的NQ查询,然后db4o还是会去转换部分的NQ查询表达式并使NQ查询尽可能的少。
有关db4oNQ查询处理器的优化工作计划所涉及的方方面面,都可以通过db4o论坛提出来。
在db4o的当前版本中,除了3.2.5所演示的以外其它示例都是经过优化后才执行的,对此我正在解决当中。
(未完待续)以下是本单元示例的完整代码