JDO 查询语言

几个词的翻译,不一定准确,呵呵。
context, 环境。
Collection,集合。有时指java.util.Collection对象,有时不是。
Class,类别。当表示一个类型的含义时,翻译成了“类别”,有type之意。

JDOQL: JDO Query Language
JDO提供的API与QL克服了其他同类技术的缺陷。
前言

Java开发者们使用序列化、JDBC和CMP持久化应用系统中的数据部分,但是这三种常用的方法都有不足之处。JCP定义了持久化Java对象模型的透明层JDO,并在2002年3月将其列入标准。在此标准中还定义了一种新的查询语言,称之为“JDOQL”,并要求所有厂商必须实现它。下面就让我们来看一下这种语言究竟是如何工作的吧。
现有持久化机制的局限当前常用的持久化机制—序列化,JDBC,CMP都有不足之处:• 序列化机制与Java语言绑定的太紧了,虽然存储对象非常方便,但是缺乏查询机制和数据库的健壮性。•JDBC提供了Java程序与SQL直接的操作接口,但它使用的是SQL的数据模型,其中只有行、列概念,不能直接存储对象。虽然JDBC确实提供了数据库具有的特性,如事务和查询,但它使用了某一中特定的SQL,从而限制了应用系统的移植性。• 开发者们使用EJB,这样他们可以使用CMP来为他们的数据建立对象模型。但是EJB诞生时的基本假定是分布式的网络计算,而在细粒度的情形下效率问题让人担忧。
    JDOQL由一组API加一个布尔型的过滤器构成,它与底层的数据库结构隔离的非常清楚。那么就是说,JDOQL在底层应该可以支持关系数据库语言SQL,对象查询语言OQL,甚至支持直接对二进制库的调用等。
    JDO定义了一个称为javax.jdo.Query的接口。在JDO的事务环境下,操作对象实例的基本接口是PersistenceManager,Query对象也就是通过此接口而创建的。在一个PersistenceManager环境下,你可以创建多个Query实例。
Query的构成

    JDOQL中查询的基本执行过程是,先对一个候选实例(candidate instances)集合进行过滤,然后返回所有满足条件的对象实例。注意这里说的是“所有”,那么是说返回的还是一个集合,即使只有一个对象也还是放在集合里面了。这一点在后文中我们还将涉及到。通常情况下,定义预先要过滤的集合范围也是必要的,那么是说候选实例集合只能是同一个类别(通过指定,可以是一个类别,或概类别的子类别)。如果想这么做的话,可以向查询中传入java.util.Collection或javax.jdo.Extent参数。
    JDO定义了Extent接口(顾名思义,这里extent是“范围”的意思),它表示数据库中的候选实例应是某一特定类别的集合。在PersistenceManager接口中有一个getExtend()方法,用于获得此对象。过滤器是一个字符串,包含的是结果为Boolean的查询表达式,可以不指定过滤器,默认结果值为true。
    PersistenceManager接口中有7个创建Query实例的方法:
Query newQuery();
Query newQuery(Class cls);
Query newQuery(Class cls, Collection c);
Query newQuery(Class cls, String filter);
Query newQuery(Class cls, Collection c, String filter);
Query newQuery(Extent e);
Query newQuery(Extent e, String filter);
除此之外,还有4个补充方法。
void setClass(Class candidateClass);
void setCandidates(Collection candidates);
void setCandidates(Extent candidates);
void setFilter(String filter); 
查询中可带有参数,而几乎所有参数的意思就是能够为变量提供运行期的值。Query接口中有一个declareParameters()的方法用来声明参数,传入的是由逗号分隔的多个包含类型    定义的参数列串。这和Java方法中使用参数的语法一样,在后面声明变量和import类的时候我们还会看到这一点。使用代码1中的数据作为例子,声明职员名称和出生日期作为参数:
query.declareParameters("String lname, Date bdate");
    在执行查询时,每个参数必须要被赋予一个值,否则会出现错误。参数值是一个Java Object,可以是原始类型的包装对象(如Float),也可是别的更复杂的对象。
    除了候选类(candidate class)外,有些时候还有必要import其他的类。Query接口中有一个declareImports()方法实现此功能,传入的是分号隔开的多个类别。
    query.declareImports("import Project;" +"import Employee" );
    每个查询中,有两个名称空间。类别名称占用一个空间,参数、域和变量名称则分享另一个空间。当使用类别时,必须要么是候选类名或与其同一package中的类名,要么是declareImports()引入类的名称。
另外在JDOQL的查询中还可以使用变量,这也是允许在运行期是赋予变量值。特别的,当一个查询过滤器包含循环整个集合时,可通过变量和contains()方法指向集合中的元素。声明变量时必须包括两个部分:变量类型和变量名称。Query接口中有一个declareVariables()方法完成此目的,传入的是分号隔开的多个变量。变量的声明类似Java中的本地变量,也就是说不带有public, private这些修饰词。在图1中,如果你想查询Department对象,并通过Employee和Project对象过滤查询结果,则应该像下面这样声明变量:
query.declareVariables("Employee emp; Project proj");
你右一次看到了,JDOQL总是尽可能的使用和Java一样的语法。
最后,可对查询结果的记录集进行排序。排序的规则也是定义在字符串内,由逗号分割的表达式组成,每个表达式带有一个ascending或descending(注意,如果这里想偷点懒,使用asc/desc是不行的啦)。最终的排序与SQL中的order by类似,先以第一个表达式排列,然后是第二个,接着是第三个这样下去。Query接口中有一个setOrdering()这个方法做这样的事情。
举个排序的例子,如果你想查询的结果先按职员所在的城市排序,然后按职员薪金从高到低排序,那么就是下面这个样子:
query.setOrdering("address.city ascending," +"salary descending");
解说过滤器

    过滤器者,过滤查询结果所用也。它由一个或多个布尔表达式组成,经过层层与/或运算后得出一个结果,这个结果还是布尔变量。如果候选类的一个对象的计算结果为真,那么它就会被选入到结果集中,等全部验证完成后返回。
    表1中列出了JDOQL中的操作符与ANSI SQL中的对应情况。表2中列出了每个操作符适用的对象类型,除了字符串链接只支持String外,其他的操作符都适用于Java中的所有对象。这些操作符在JDOQL中的使用与Java中类似,但并不完全相同。这里说明两点不同,之一是JDOQL中允许原始型变量与其包装对象进行直接比较,比如int型与Integer的比较,是有效的。数值型对象(如BigDecimal和BigInteger)与包装型(如Integer和Long)之间进行相等比较,大小比较和算术计算时,都是针对他们内部的值进行的。数值型的操作数在比较时是自动向上造型的,举个极端的例子,byte可以和BigDecimal比较。之二是JDOQL中允许String间和Date间通过操作符直接进行比较,Java中则不具备这个能力。
    实例之间进行比较时,首先要看实例的类别是否实现了PersistenceCapable接口,如果类别的实例要持久化,则他们都被增强(enhanced)实现了此接口。如果在这样的两个实例之间比较时,实际上比较的是实例的ID标识。换句话就是,如果实例的ID相等,则他们就被认为是相等的。
    两个String的方法,startsWith()和endsWith(),被JDOQL实现了用以支持模糊查询。如表达式:
    lastname.startsWith("Jo");
查询所有以名字”Jo”开始职员,如Jones, Johnson, and Joyner。
    虽然JDOQL只对单个类别的实例进行查询,但并不是说不可以在查询中使用其他的类。实际上,我们可以通过实例所关联的对象来加重对结果的约束。那么,怎么实现呢?在Java语言中,通过点号(.)进行连接,如manager.getAddress().getStreet(),当碰到null对象时抛出NullPointException。JDOQL中的情况类似,也通过点号(.)连接,如manager.address.street,但当碰到null对象时,只表明过滤器中当前的布尔表达式为假。
    如果数据模型中有一个集合对象,当想判断其是否含有元素时,可调用isEmpty()方法检查。如表达式:
    members.isEmpty();
判断Project类别的一个实例中是否包含有成员。
遍历模型中集合的对象也是可以的,可使用一个变量指向集合中的当前元素,因此在查询中为每个集合声明一个变量是必要的。与变量关联的方法contains(),实在是相当简单。如果集合非空并且至少包含一个对象,contains()方法就会返回true。constains()方法必须作为AND表达式的左侧操作数,而变量则作为右侧操作数。在这个过滤器例子中,候选类别是Department,并且假定已经通过declareVariables()声明了emp和proj这两个变量:
employees.contains(emp) & emp.projects.contains(proj) 
& (proj.budget > 1000000.00 & proj.name.startsWith("Software"))
看了上面的例子,你似乎觉得一点点不对劲。终于,你想起来了,后面两个表达式有必要放在一个括号内吗?你的疑惑是对的,但是这里是一个特殊情况:变量proj此时已经代表集合中的一个元素,只能作为AND表达式的右侧操作数。
编译、执行

应用程序在执行查询前,可先对查询进行编译。这是可选的,并不要求你一定如此。一旦编译,与Query实例相关的所有元素都会得到校验,任何校验错误都会导致JDOUserException的产生。如果查询被执行多次,则编译一下是有好处的,此时查询会得到预备和优化(prepared and optimized)。
执行查询的方法由Query接口提供,这里不止一个方法,他们形式上稍有不同。不超过三个参数的查询可以直接通过execute()方法执行:
Object execute(Object p1);
Object execute(Object p1, Object p2);
Object execute(Object p1, Object p2, Object p3);
如果多于三个参数,另有executeWithMap()和executeWithArray()两个方法。这在稍后会有介绍,让我们先研究一下execute()这个方法。
    传入execute()方法的参数按照与被声明时相同的次序一一映射,每个参数都是一个Object。传入execute()方法的参数只能被使用一次,执行一次之后就被丢掉了。再次执行时,虽然是相同的查询,仍然要重新指定参数。
    execute()方法执行的结果一个固态集合对象(unmodifiable Collection),但返回的是一个Object引用。这么做是为了将来扩展而考虑的,那时当结果是单个实例时,可能返回的就不是一个集合对象了。 因此,我们要做的就是把结果造型回Collection,遍历其元素进行相关操作。任何试图改变集合的操作都会引发UnsupportedOperationException 。
    这里有另外两个方法可以执行查询。当传入的参数个数多于三个时,或者你就喜欢这种方式时,可以使用它们:
Object executeWithMap(Map m);
Object executeWithArray(Object[] a);
executeWithMap()方法传入的是一个包含键-值对的Map对象,键是参数的名称,值是查询执行时的对应的对象。executeWithArray 与execute()方法相似,按参数声明时的顺序意义映射(可以想像其中的道理,Map是以主键作为索引的,而数组是顺序存储的)。
小结

代码2提供了执行复杂查询的相当完整的例子,例子中假定已经获得了PersistenceManager对象,并启动了一个事务。这个查询搜索所有家住Georgia,年薪6位数,在网络部门工作并且所在项目的预算超过一百万的公司职员,查询的结果按照部门编号组织在一起,部门内再按薪金高低排序。
JDOQL是一个相当简单的语言,可以估计在下一年将会得到广泛的业界支持。它的设计思路,接口和表达式都模仿自Java语言,这样对Java开发者们来说,应该相当易学。有时在语义相近但数据类型要求严格时,为了改进和简便,它的语法又比Java更简单。JDO实现的计划将包括很广的数据存储,包括层次型,关系型和对象型。JDOQL是JDO实现中要求的一部分。
作者简介

David Jordan自1981年以来一直从事面向对象软件的开发,自1985年以来开始涉及对象持久化和数据库方法的技术。他是JDO专家组成员的积极分子,并创立了Object Identity公司,以专门提供JDO开发支持的咨询服务。
======================================================================
关于JDO2.0中的JDOQL

这是2002年七月的一篇文章,我从www.javapro.com上下载的,具体路径我已经不记得了。根据今年8月份2.0版本讨论会后发表的文章,JDOQL应该会得到进一步改进。这里先列举一些概要,相信不久就会有草案出来的。
1. 操作数据库

JDO的出现会损害到那些数据库高手或者DBA的利益吗?他们会因此而抵制公司采用JDO吗?当你请教你们的DBA,而他却说“我不懂JDO,我只会SQL”时,当你想使用一个怪异的查询却在JDO找不到对应的方法时,你会因此而黯然心灰,凄然落泪吗?
2.0或许能帮助你解决当前的困境(不过还要等等J),看一下下面的例子:
Query q = pm.newQuery("javax.jdo.SQL", 
"SELECT * FROM person WHERE name LIKE '%Beckham'");
Query q = pm.newQuery("javax.jdo.SQL", "CALL GET_BECKHAMS");
不仅支持SQL,还将支持OQL,VQL(Versant,名字为“熟悉的”,但我不熟悉J)。但是这样的查询执行的结果是什么呢?对SQL,会是一个java.sql.ResultSet吗?二维数组,还是新定义的一个对象类型?
2. Query接口

Query接口肯定将回加入更多的方法。
setResult("count(this)") ,setGroupBy("x,y,..") ,executeUnique()
除现有startsWith()和endsWith(),应该还有增加一下String的方法。还有其他的吗?不知道,应该挺多吧... ...
关于JDO2.0的参考链接:

http://www.theserverside.com/resources/article.jsp?l=JDO2-Kickoff
http://www.theserverside.com/resources/articles/JDO2-Kickoff/meeting-minutes.html
http://www.theserverside.com/home/thread.jsp?thread_id=20978
posted @ 2009-09-23 23:31  senzjx  阅读(1906)  评论(0编辑  收藏  举报