JDO应用介绍
by Teresa Lau
译自:http://www.sys-con.com/java/articleprint.cfm?id=1899
(译者注)本文以一个实际的具有一定相互关系的类结构实例和KodoJDO产品作基础,介绍JDO的原理、使用及特点。尤其是与传统JDBC技术的对比,比如一个类的代码从480行减少到140行的现实,说明JDO对代码的减少。另外,本文作者是一位具有五年以上Java顾问经验的女中豪杰,拥有硕士学位,目前工作于纽约。
Java Data Object(JDO) 是一个用于存取某种数据仓库中的对象的标准化API。
使JDO从各种数据对象存取技术中异军突起的是它的高度的易用性和灵活性。
JDO提供了透明的对象存储,因此对开发人员来说,存储数据对象完全不需要额外的代码(如JDBC API的使用)。这些繁琐的例行工作已经转移到JDO产品提供商身上,使开发人员解脱出来,从而集中时间和精力在业务逻辑上。另外,JDO很灵活,因为它可以在任何数据底层上运行。JDBC只是面向关系数据库(RDBMS???DO更通用,提供到任何数据底层的存储功能,比如关系数据库、文件、XML以及对象数据库(ODBMS)等等,使得应用可移植性更强。
概述
描述符(Metadata)和增强器(Enhancer)
在JDO中,任何需要存储的类必须是PersistenceCapable(可存储的),而任何用到这些类的其它类必须是PersistenceAware(存储可知的)。看上去有点复杂,不过好消息是采用JDO的透明存储技术,你不需要在代码中将你的类去实现javax.jdo.PersistenceCapable接口或PersistenceAware接口。你只需要象往常一样写一个类即可,JDO厂商会提供一个增强器,这个增强器会改造你的类代码(译者注:就是.class文件,改造class中的二进制代码并不是一件很高深的事,有很多工具可以提供给你对class文件进行改造,比如在某个方法开始和结束时输出日志等等),根据你的描述符使之实现PersistenceCapable接口。你需要做的唯一的额外工作就是为你需要存储的类写一个XML格式的描述符文件。附件1显示了一个我在后面的代码示范中会用到的描述符文件。(附件17和本文的代码可以在www.sys-com.com/java/sourcec.cfm下载。)
描述符一般来说很短,在默认条件下写起来一点也不难,JDO从类本身已经获取了大量的信息。你仅在下列情况下在描述符中添加额外信息:
。你需要改变JDO的默认操作方式,比如,使一个属性不存入数据库,即使它没有标记为transient
。有些额外的信息是JDO无法从类本身获取的,比如:哪一个属性你想设为数据库中的主键(译者注:只有使application identity时才有这个必要),或者某个集合类型(Collection或子接口/类)中的元素是什么类型
存储管理器(PersistenceManager)
当你的类被增强后,你就可能使用PersistenceManager来存储你的对象了。要获取一个PersistenceManager,先要设置一些属性,通常包括下列信息:
。数据库连接信息
。JDO产品的类名
。默认的一些属性,包括连接池大小等等
附件3的第23行显示了如何设置一个Properties对象,从而获取一个JDO的PersistenceManagerFactory(相当于JDBC连接池中的DataSource),进而获得一个PersistenceManager对象(相当于JDBC中的Connection对象),之后,你可以用这个PersistenceManager对象来增加、更新、删除、查询对象(这也是我在后面将会讨论的)。当你的这些操作做完后,你需要关闭主这个PersistenceManager对象,以释放它使之能被再次使用(比如另一个线程)。
附件3的代码片断告诉你如果使用JDO来存储和查询对象。利用一个PersistenceManager类型的对象pm,你可以使用pm.makePersistent()方法来将一个新的对象保存到数据库(第68行)。一个对象仅仅在第一次出现的时候才需要保存到数据库(译者注:更严格一点,只在没有已存储的对象引用时才需要显式地调用makePersistent()方法),当它已经在数据库中存在以后,可以直接通过访问其属性来更新该对象的信息。所有的更新在当前的Transaction(数据库概念:事务)被提交时全部保存到数据库中。而如果你不希望保存主这些更改,只需要简单地rollback当前的Transaction即可(第15到17行)。类似地,你可以调用pm.deletePersistent()删除一个对象(第26行)。
要访问已经存储过的对象,可以简单地遍历其Extent(类的所有扩展),这是一个对该类对象的逻辑上的总称(第12到15行)。
如果你希望有选择地(而不是一古脑地)取出某个类的所有对象的一个子集,你可以创建一个Query(查询)。要做到这一点,调用pm.newQuery()来获得一个Query对象,并传入参数:候选对象集合和一个过滤器。候选对象集合可以是一个Collection或者一个类的Extent。过滤器是一个JDOQL(JDO Query Language)语句。当你创建了这个Query以后,就可以执行它来获得一个符合条件的集合(Collection,第22到26行)。JDOQL是JDO的查询语言;它有点象SQL,但却是依照Java的语法的。这里的例子只是一个简单的示范;使用JDOQL,你的过滤串可以写得很复杂。另外,如果你在过滤串中使用绑定的参数的话,你可以写一个简单的查询然后执行很多次,每次给出新的参数值。关于JDOQL有很多资料可以参考,参见本文的资源列表。
一个对象存储的例子
要找出JDO是否象Sun说的那么好,我会写一些代码,分别使用JDO和JDBC来存储我创建的Book对象(见附件4)。这个Book对象有一个name属性和一个Block对象。为使示例有趣一些,Book对象有一个限制:每本书由其名称唯一确定,也就是说,你不能加入两本同名的书。
一个Block(块)是Book的组成部分,它可以是Document、Chapter或Section。最顶层的Block是Document类型的,并且可以包括任意数量的Chaptor Blocks。每个Chaptor Block可以包括任意数量的Setion Blocks,因此这些Block中有一种嵌套的关系。在每一个Block中,我们用一个HashMap来存放任意数量的其它Block的键值对。
附件5列出了一个我的例子中做测试用的Book。这本书包括两章(Chaptor),第一章有一节(Section),还有第二章。特别地,第二章有一个属性:Color=Red。
用这个图书的例子, 我要实现以下一个存储功能:
。增加:看看我能否成功地向数据库中添加两本书,并且如果我用同样的名字加入第三本书,将会产生一个名称唯一性检查失败的异常
。更新:看看我是否能够更新一本书:增加一个属性“Comment”到它的根Block中。当我提交(commit)的时候,这些更新应该被保存下来,而如果我回滚(rollback),这些更新将被丢弃。
。删除:看看能否通过Query查询一本书然后从数据库中将它删除。
如果没有JDO, 我通常会这样设计:先设计几个相关的关系数据表来存储书中的数据,然后使用SQL和JDBC来存储/读取这些表。由于长度的关系,我不会在这里列出我的JDBC实现,不过如果感兴趣,你可以从JDJ 网站上下载。注意,为了通过JDBC/SQL实现以上这些功能,我必须写很长一段代码(480行!)。我现在要做的是让你看看用JDO来实现同样的功能会有多么的简单。
用JDO存储一个Book对象
用JDO来存储一个Book对象时,尽管我使用与JDBC方式中同样的Book主键类,但Block的ID属性已经完全没有必要。在JDBC方式中,必须用ID属性来在内部引用数据表中的不同的Block。但在JDO中,我根本不需要这个属性,因为JDO会自动地在底层处理它。
为了标明Book对象是需要存储的,我写了一个描述符来标记Book和Block类(见附件1)。在描述符中,我为Block类的children集合属性标明其元素类型是Block(第10到11行),而HashMap的键和值的类型都是String(第12到14行)。此外,由于ID属性并不是Block真正需要的,我在描述符中标明它不需要存储(第8行)。在Book的描述符片断中,我标明nm(名称)属性是它的主键(第5行),因此Book类需要使用自定义的标识类型(Application Identity),类名是BookKey(第4行)。BookKey类的代码见附件6。
在本例中,我使用的JDO产品是KodoJDO(采用关系数据库作底层)。市场上有很多JDO的产品(Implementation);你可以选择其中任意一种,而你的代码不需要作任何变化。底层的关系数据库我选择Enhyda InstantDB(一个Kodo产品附带的关系数据库)。JDO的精髓在于开发人员不需要知道某个JDO产品是如何将数据存入数据库的,所以我也不需要设计任何数据表,尽管我们底层使用的是关系数据库。Kodo提供了一个名为schematool的工具,根据我的描述符自动地创建需要的数据表结构。我所需要做的全部事情就是运行下面的命令来准备数据库底层(译者注:实际上,KodoJDO2.4.0以上版本就可以完成自动的数据库同步,这一步都可以省略。不过只建议在开发时使用):
schematool action refresh Book
schematool action refresh Block