JDO(Java Data Object)是JCP中较早开发出来并形成规范的JSR12,该规范对数据的持久化存储进行了一系列规范,并已有众多的商业产品和开源项目是基于该规范。作为一种需要引起重视的技术,研究并探讨其企业应用可行性是十分重要的。
前言
在企业级的应用开发中,常需要有良好的持久化技术来支持数据存储。通过良好的规范或API,将企业的领域业务对象进行持久化存储,大多采用O/R映射技术来进行模式化的数据转换及自动映射工作。
JDO(Java Data Object)是JCP中较早开发出来并形成规范的JSR12,该规范对数据的持久化存储进行了一系列规范,并已有众多的商业产品和开源项目是基于该规范。作为一种需要引起重视的技术,研究并探讨其企业应用可行性是十分重要的。
以下主要对JDO(JDO 1.0规范)的应用开发技术作扼要介绍,通过该文,可以由浅入深、并较为全面地了解JDO,掌握主要的技术细节及过程,理解其运行机制,并对企业级应用有个总体的把握,这将有助于企业应用软件的技术选型、体系架构及分析设计活动。
该文适合企业应用架构师、及关心数据持久层设计开发人员。
JDO基本思想及特点
企业信息系统的一个重要问题是解决数据的存储,即持久化。在软件开发过程中,分析员分析领域业务,提取出领域业务模型,并对应设计出数据库中需要进行存储业务数据的数据库表及相应字段。
并根据业务流程,设计业务处理逻辑单元,进行数据的加工、处理及存储、查询等业务。其中一个较为繁烦、枯燥的工作,就是处理大量的数据持久化代码。
为了解决数据从业务对象层向数据存储层之间的转换工作,JDO提供了相应的开发规范及API,解决了由Java对象直接存储为数据库相应表的底层处理过程,有助于设计人员更加专注于面向业务流程、面向业务对象等较高层次的应用。
由于采用JDO的映射机制,能降低了业务系统与数据存储系统的耦合,使得业务系统相对于关系数据库或对象型数据库,具有可移植性,同时,由于采用面向对象(而非传统的面向记录)的持久化技术,系统更为轻便、简洁,增强了可维护性。
JDO应用示例及分析
以下将通过一些示例,由浅及深地讲解JDO技术。
临时对象与持久对象
这是一个普通的业务对象的代码。
package business.model; public class Book { private String isbn; private String name; private Date publishDate; public void setISBN(String isbn) { this.isbn = isbn; } public String getISBN() { return this.isbn; } public void setName(String name) { this.name = name; } public String getName() { return this.name; } public void setPublishDate(Date pubDate) { this.publishDate = pubDate; } public Date getPublishDate() { return this.publishDate; } }
现在将它作为一个JDO中对象保存到数据库中。代码如下:
Book book = new Book(); book.setISBN(“isbn-1234567”); book.setName(“Java设计模式”);
PersistenceManager manager = persistenceManagerFactory. getPersistenceManager(); manager.currentTransaction().begin(); manager.makePersistence(book); manager.currentTransaction().commit();
Book类的实例book对JDO的API而言,就是一个持久对象。类Book是可持久类。那任何一个普通java类都是JDO的可持久类吗?不是的。只有具备以下的条件,一个对象才可以被JDO持久到数据库中。
它所属类应标记为可持久的类,有以下两种方法:
显式:实现接口,javax.jdo.PersistenceCapable即可;
隐式:以Sun的JDO参考实现为例,Book.java类的相同路径下还须有Book.jdo文件。
encoding = “UTF-8”?>
并通过字节码增强工具(本例采用Sun的字节码增强工具)处理:
javac Book.java java com.sun.jdori.enhancer.Main Book.class Book.jdo
通过上述两种方法,获得的Book.class才是一个可持久的类。
字节码增强的有如下功能:当应用程序通过set方法修改某个字段1时,由于通过增强过程,在其内部插入了某些代码,JDO会获得数据状态变化的信息,从而在持久过程中,进行有选择性的处理。
按照JDO规范,增强后的类可以在不同的JDO实现上使用,而无需重新编译或增强。
并不是所有Book对象都是持久对象,只有当makePersistence后,该对象才是持久对象,并会通过JDO实现存储到数据库中。通过JDO的供应商扩展标记符(vendor-extension),可详细描述Book类的存储特性,如为该可持久类指定数据库表和对应字段。
持久对象查询
JDO查询主要有以下两种方式。
使用Extend查询
Extend可以查询指定类及子类的持久对象。
PersistenceManager manager = persistenceManagerFactory. getPersistenceManager(); manager.currentTransaction().begin(); Extend extend = manager.getExtend(Book.class,true); //true表明同时查询子类 Iterator it = extend.iterator(); while(it.hasNext()) { Book book = (Book)it.next(); System.out.println(book.getISBN()); } extend.closeAll(); manager.currentTransaction().commit();
Extend查询方法,提供了一种基于类的查询途径,它可以与下面的Query构成更为强大的查询。
使用Query查询
Query可以指定过滤条件,是一种常用的查询方式。
下例是查找条件为“书名以‘Java设计模式’开头且出版日期小于今天”的书籍。
String filter = “((String)name).startsWith(\”Java设计模式\”) && publishDate < today”; Query query = pm.getQuery(Book.class,filter); query.declareImports(“import java.util.Date”); query.declareParameters(“Date today);
Date today = new Date(); results = (Collection) query.execute(today); //传入参数值today if (results.isEmpty()) { System.out.println(“No data!”); }else{ Iterator it = results.iterator(); while(it.hasNext()) { Book book = (Book)it.next(); System.out.println (“Book Name:” + book.getName() + “, ISBN:” + book.getISBN()); } }
注:该条件使用了一个变元‘today’,通过“declareParameters”来声明该变量,并在“execute”方法中传入该变量的实例。这种带参数的查询,很类似于我们以前采用JDBC的带?的查询方式。
其中startsWith(String s)是JDO提供的标准字符方法,类似的方法还有endsWith(String s)。
JDOQL:上述使用的就是一个JDOQL样例,JDOQL是JDO规范一个组成部分。使用JDOQL可以使用应用在不同的JDO实现上运行。为了解决JDOQL的某些不足,JDO规范提供了支持特定JDO供应商查询语句接口。
查询排序
下例是将查询结果按“出版日期降序、书名升序”进行排序。
Query query = pm.newQuery(Book.class, filter);
String orderStr = “publishDate decending, name ascending”; query.setOrdering(orderStr);
results = query.execute(today);
对象更新
当客户端对业务数据进行了更新后,需要通过业务过程将其更新到持久层中。这有两个过程,首先根据主键找到该实例,接着更新字段及提交。如下例,将指定书目编号的书本的出版日期进行更改。
public void updateBookPublishDate (String isbn, Date newDate) { PersistenceManager pm = null; try{ pm = pmf.getPersistenceManager(); Object obj = pm.newObjectIdInstance(Book.class,isbn); Book book = (Book)pm.getObjectById(obj,true); book.setPublishDate(newDate); }catch(Exception e) { xxxContext.setRollbackOnly(); throw new Exception(e); }finally{ try{ if (pm != null && !pm.isClosed()) { pm.close(); } }catch(Exception ex) { System.out.println(ex); } }
注,在PersistenceManager使用newObjectIdInstance()方法时,JDO是如何知道通过书目编号ISBN来找到该对象呢?其实在本可持久类Book的jdo描述文件中,还需提供如下信息:
encoding = “UTF-8”?>
identity-type=“application” objectid-class=“BookKey” > primary-key=“true”/>
其中“identity-type=“application””声明可持久类Book采用程序标识方式,即应用程序传入ID(字段isbn为“primary-key”)信息,JDO实现构造出指定的“objectid-class”的实例(即newObjectIdInstance过程),并由JDO来检索出指定的持久化对象(即getObjectById)。
BookKey类源码如下:
package businesss.model; public class BookKey implements java.io.Serializable { public String isbn; public BookKey() { } public BookKey(String oid) { isbn = oid; } public String toString() { return isbn; } public Boolean equals(Object obj) { return isbn.equals ((BookKey)obj).isbn); } public int hashCode() { return isbn.hashCode(); } }
符合 JDO 的“objectid-class”类,如“BookKey”,须具备以下条件:
类声明为 public,并实现 java.io.Serializable;
带有一个公有且不带参数的构造方法;
当字段作为主键时,须有公有的,且名称和类型与持久类的字段一致,如:public String isbn;
equals 和 hashCode 须使用全部(特指多字段的联合主键)的主键字段值;
类必须有一个构造方法,与 toString 方法的处理过程是逆向过程;即将 toString 的输出值,作为该构造方法的输入值,又可以重新生成该实例(如构造方法“public BookKey(String oid)”)。
综上所述,如果Book由两个字段作为主键,如isbn和name,则可能的代码是pm.newObjectIdInstance(Book.class,isbn+“#”+name),且BookKey的构造方法作相应更改,并有两个公有字段“isbn”和“name”。
对象删除
对象删除采用方法deletePersistence。示例如下:
pm.currentTransaction().begin(); Object obj = pm.newObjectIdInstance (Book.class,isbn); Book book = (Book)pm.getObjectById(obj,true); pm.deletePersistence(book); pm.currentTransaction().commit();
获得PersistenceManager实例
上述的所有操作与需要PersistenceManager实例,它可以在两种环境方法下获得:非受管环境和受管环境。
非受管环境
非受管环境是多指两层开发模式,应用程序直接获得资源对象,进行业务操作。一般事务管理、安全管理或资源管理都需要应用程序自行维护。
Properties properties = new Properties(); properties.put(“javax.jdo. PersistenceManagerFactoryClass”, “com.xxx.jdo.xxxPMFClass”); properties.put(“javax.jdo. option.ConnectionURL”, “xxx”); properties.put(“javax.jdo. option.ConnectionUserName”, “xxx”); properties.put(“javax.jdo. option.ConnectionPassword”, “xxx”); PersistenceManagerFactory pmf = JDOHelper.getPersistence ManagerFactory(properties); PersistenceManager pm = pmf.getPersistenceManager();
与JDBC获取类似。
受管环境
受管环境一般是多层开发模式,尤其是处于J2EE应用环境中,程序通过容器获得资源对象,进行业务操作,由于在容器环境下,事务、安全及资源管理都由容器进行统一集中管理,从而简化了代码结构。
以下是EJB(EntityBean、SessionBean、MessageDrivenBean)中的setXXXContext中的代码示例。
private PersistenceManagerFactory pmf; public void setXXXContext (XXXContext context) { try{ InitialContext ctx = new InitialContext(); Object obj = ctx.lookup (“java:compenvjdofactory”); pmf = (PersistenceManagerFactory) PortableRemoteObject.narrow (o,PersistenceManagerFactory.class); }catch(NamingException e) { throw new Exception(e); } }
PMF是如何绑定到J2EE环境下的JNDI上,有兴趣可参考JCA相关的技术文档。
事务管理
事务管理及使用,主要有以下三种情形。
使用JDO事务的Bean管理情形
一般在非J2EE环境下,使用该事务管理模式。
PersistenceManager pm = pmf.getPersistenceManager(); pm.currentTransaction().begin(); //do some business with jdo pm.currentTransaction().commit(); pm.close();
使用JTA事务的Bean管理情形
一般在J2EE环境下,采用Bean管理的事务情形下,使用以下方式。
该方式可用在EJB的事务环境下。
xxxContext.getUser Transaction().begin(); PersistenceManager pm = pmf.getPersistenceManager(); //do some business with jdo xxxContext.getUserTransaction().commit(); pm.close();
使用JTA事务的容器管理情形
一般在J2EE环境下,采用容器管理的事务情形下,使用如下方式。
如下是某个会话Bean的业务方法。
public void doBusiness(){ PersistenceManager pm ; try{ pm = pmf.getPersistenceManager(); //do some business with jdo }catch(Exception e){ xxxContext.setRollbackOnly(); System.out.println(e); }finally{ try{ if (pm != null && !pm.isClosed()) pm.close(); }catch(Exception ex){ System.out.println(ex); } } }
综上,可以得出结论,JDO的操作完全与JDBC的操作相差无几。
JDO高级应用
复杂对象的持久化
在实际的应用中,一个可持久化类要远比Book类复杂很多。它可能会引用其它的Java类型、类、集合或数组,及可能复杂的继承关系。当这些对象的状态发生变化时,JDO是如何感知及跟踪状态变化?
JDO提供了相应的API及规范来实现该功能。
基本类型及引用
可持久化类的字段能被JDO实现进行持久化的原则。原始类型、java.util.Date等被支持(其它较为复杂或可选特性,详见JDO规范);如果引用是一个可持久类,则JDO进行持久化处理;通过元数据(如jdo文件)声明的字段,一般是非可持久化类的引用,JDO进行持久化;
前面两种情形,当状态发生变化时,JDO会自动感知,但如果引用是非可持久化类,则需要代码进行显式通知,否则JDO不会将变化进行存储。如下例:
public class Book { …… private Object picture; public void setPicture(Object pic) { picture = pic; } public Object getPicture() { Return picture; } }
该类字段picture需要持久化,但java.lang.Object不是一个可持久类,故声明如下:
persistence-modifier=“persistent”/>
如果其它模块通过getPicture获得对象,并在JDO不可感知的外部,修改对象,则变化不会存储,较好的办法是修改setPicture方法,如下:
public void setPicture(Object pic) { JDOHelper.makeDirty(this, “picture”); picture = pic; }
并通过setPicture方法来更新数据。JDO的“makeDirty”方法,主要负责通知JDO实现,可持久化类Book某个实例(this)的“picture”字段发生了变化。
集合
可持久类的字段引用为集合时,按照JDO规范,强制支持java.util.HashSet,对HashMap、HashTable、TreeMap、TreeSet、LinkedList、ArrayList及Vector的支持对JDO实现而言是可选的,通过PersistenceManagerFactory的supportedOptions方法获得实现特性。
数组
如果可持久类的引用是数组类型,当数组单元发生变化时,需要调用“makeDirty”来通知JDO实现,该实例的数组引用内容发生了变化。与引用是非可持久类实例不同,不需要进行JDO的元数据声明。
继承
如果使用可持久性,一般继承的子类与父类都为可持久类,以减少系统复杂性,这时需要在子类的元数据中指出其可持久超类,如下:
< class name=“TechBook” persistence-capable-superclass=“Book”/>
可为非持久类扩展持久类,或可为持久类扩展非可持久类;这两种情形JDO实现都将忽略非
可持久类的字段部分,而不保存到数据库。
JDO的一些不足之处
JDO对数据的持久化技术相比于成熟的SQL,还有不足之处,这些不足在某些情况下将可能会影响数据处理部分的设计实现。以下列举了常用数据访问的必要功能
查询增强
如字符串不支持通配符、大小写比较;
不支持聚合操作,无法实现MIN、MAX、SUM和AVG;
不支持投影操作,JDO查询返回为对象,而不像SQL那样返回字段;
不支持联合、交叉(UNION/INTERSECT);
不支持JOIN、IN和EXISTS;
企业应用探究
由于JDO采用面向对象的持久化处理技术,从而为解决企业业务系统的持久化问题提供了一个新技术解决方案。但是先进的未必就最适用。在某些应用场景下,需要结合各种因素,采取灵活的策略。
面向对象与面向记录
现在大多业务系统都采用面向对象分析设计,这就需要每个应用系统都自行实现将对象映射成记录,并存储到数据库中,而有JDO这样的面向对象的持久化技术,在某种程度上解放了这种转化设计的不规范性,进而获得相对更优的系统结构。
另一方面,一个业务系统的数据持久化,一般都有这样的过程:对象层->记录层->物理层,JDO无疑使分析设计人员从记录层的苦海中解脱出来,从而更加专注于对象层,这对开发人员无疑是一个令人欢欣鼓舞的技术。
JDO并不能完全代替JDBC。
根据经典的“8-2原理”,如果用JDO来解决80%的问题,余下的20%由JDBC来实现,这种相互补充,各取所长的策略,是一个很有效的办法。
这样一方面可以获得较好的结构及提升开发质量,另一方面解决了JDO的某些技术不足,并可根据以后的技术变化,再做适当转化。
性能问题
JDO与JDBC究竟谁的性能更优,目前还没有一个权威、且令人满意的答案。但对于一些JDO实现而言,通过采用缓存机制,使得性能有了较大提高。
跨数据库
使用JDO的系统能更好地进行数据库移植,甚至可以在对象数据库上运行;当然JDBC处理层如果完全遵循SQL-92标准,也同样具有很好的跨数据库能力。 (责任编辑:包春林)
|