摘自久久学院
1 Java与数据库应用,JDBC
Java发明以来,在短短的几年之间,迅速占领了从桌面应用(J2SE)到服务器(J2EE),再到小型设备嵌入式系统(J2ME)的应用开发市场,其语言吸取了SmallTalk的一切皆对象的理念,摆脱了C++的历史累赘,简洁、自由的风格赢得了很多开发者的喜爱。从JDK1.1开始,Java成为实用的语言,而不是被人观望的新品秀;再经过JDK1.2的大量增强(尤其是Collection Framework),JDK1.3的虚拟机效率提升(HotSpot),JDK1.4的融合百家之长(Logging、RegExp、NewIO等),现在已经是成熟稳重,颇显大家风范。
在企业级市场上,大部分的应用建立在数据库基础上,数据是企业的生命,传统开发语言,包括面向过程的C、面向对象的C++、变种Pascal的Delphi(非常棒的语言,我用过四年),面向数据的PowerBuilder等等,先后在数据库开发的舞台上展现风姿。Java当然不会放过这些,于是,出现了JDBC。在JDBC的帮助下,Java也迅速渗入数据库开发的市场,尤其是面向企业服务器的应用开发。
今天要谈的JDO,与JDBC有非常密切的关系,尽管JDO并不是只面向JDBC的数据对象包装规范。下面先简单地介绍一下JDBC。
1.1 关系数据库之百家争鸣,ODBC
关系数据库的历史一言难尽,我只能从我的接触经历和所见所闻,简单地叙述一下。最早的时候,计算机还只在一些大型的研究机关露面,并不是普罗大众可以涉及的。苹果电脑将个人电脑引入民间,再随着IBM的PC标准开放,个人电脑逐步普及开来,加上微软的DOS操作系统,以及Borland的Turbo系列语言开发环境,老百姓发现原来电脑可以做这么多事!后来,出现了DBASE,一个简单的关系数据库系统,和SQL语言。后来,Borland看到了数据库的市场前景,推出了Paradox(也是当今Delphi和C++Builder中仍然使用的Paradox),一举占领了民用数据库的大部分江山,之后,Borland干脆收购了Dbase,后来又购买了InterBase,将数据库市场的领先优势一直保持到Windows3.0出现。这时候,微软在Windows1.0和2.0被人痛骂之后顽强地推出3.0,以及更稳定的3.1和Win32API,造就了个人电脑桌面操作系统的霸主地位,在Borland未警觉的情况下,购买了同样具有类Dbase数据库技术的Fox公司,并迅速将其易用化,形成了FoxBase,后来演变成FoxPro,逐渐超过了Borland,成为个人电脑数据库的大户。微软再接再励,为简单易用而低负荷要求的数据库应用开发了Access,赢得了广大开发人员的心。当然,同期的Oracle、Sybase、Informix等商用数据库凭专注于企业级数据库技术成为高端的几位领军人物。微软当然也想成为高端数据库供应商之一,于是自行开发一套面向企业级应用的数据库,不过很快项目夭折,微软不甘心,购买了Sybase的底层TDS技术,包装成了SQL Server,凭微软的高度易用性的特点,也占领了不少市场。
当市场上出现众多的数据库产品之后,Borland和微软都发现自己拥有的数据库产品挺多,市场也不小,不同的产品给用户带来不同的配置任务,不利于所有产品的推广,于是,两者纷纷开始制定数据库访问的规范,微软推出了ODBC,其面向开发人员的亲和性,逐步获得了认可,同时,Borland纠集了IBM和Novell也推出了IDAPI数据库接口规范,也就是今天BDE的核心,不过后来Novell和IBM先后退出,只剩Borland独力支撑。不过Borland是一个技术实力雄厚的公司,其技术一向领先于微软,BDE的性能比初期的ODBC不知道要好多少倍,后来微软偷师学艺,把连接池等技术加到ODBC中,在Delphi3.0及其BDE在市场上风光无限的时候,逐步赶了上来并有超过。直到今天,BDE仍是Borland的产品线上的数据库访问标准,而微软如果不是将ODBC和多数数据库的客户端内嵌进Windows的话,估计BDE仍是市场的赢家。不过,微软是玩弄市场的老手,通过对操作系统的垄断,其数据库产品和ODBC标准终究占据了多数开发市场。
1.2 从optional pack到JDK的标准API
Java开始涉及数据库应用后,Sun就极力制定Java的数据库规范,JDBC API就是类似ODBC一样,对数据库访问的底层协议进行最基本的包装,然后形成一套统一的数据访问接口,数据库连接、SQL语句句柄、结果集,都带有ODBC的影子。以方便配置为目的,Sun极力推荐完全瘦客户端的TYPE 4型JDBC驱动,这是一个不需要安装数据库客户端的驱动规范,是现在使用最多的。当然,为了保持与旧的数据库兼容,JDBC规范中包括了专用于连接ODBC的TYPE 1驱动和需要安装数据库客户端的TYPE 2驱动,以及可以由厂商在数据库服务端专门提供面向JDBC的服务的TYPE 3驱动。
JDBC最早出现时,还不属于标准JDK的一部分,而是作为一个额外包提供下载。后来,随着Java编写的数据库应用的的增多,和JDBC规范本身的逐渐成熟,JDBC终于成为JDK1.1的一部分。
JDBC目前最新的是3.0版本,还有正在讨论中的4.0版本。实际上,在开发中使用得最多的还是1.0中的API,2.0中主要增加了可双向滚动的结果集、更新批处理等提高可用性和性能的API,3.0主要增加了连接池、可更新的结果集等特性。4.0将在可管理性、连接池规范化等方面再做改进。
2 面向对象与数据库
现在的程序员,没有不知道面向对象的。作为接近真实客观世界的开发概念,面向对象使程序代码更易读、设计更合理。在普遍存在的数据库应用领域,开发人员对面向对象的追求从未停止过。从八十年代开始,就有很多公司和研究机构在进行着面向对象与数据库结合的研究。
2.1 SmallTalk、C与C++、Delphi-Object Pascal、Java
面向对象的语言最早有好几种雏形,IBM的SmallTalk是其中最为流行的,在SmallTalk中,一切都是对象,一切都是类,它将面向对象的概念发挥到了极致。面向对象的编程比起传统的面向过程的方式挺进了一大步,使人们认识到:原来软件可以这样写。不过,由于计算机基本结构与底层硬件体系和系统软件的限制,SmallTalk还不能在理想的性能前提下推广到普通的应用上,这一点暂时限制了SmallTalk的发展,接着,C语言的面向对象版C++出现了,由于使用C语言的人很多,C++很快成为面向对象编程的主流语言。不过,为了保证与C的兼容,C++保留了很多面向过程的痕迹,比如恶心的指针、全局变量等等。Pascal的改进版Object Pascal相对来说安全许多,后来Borland干脆将Object Pascal换了个名字,叫Delphi,从此开创了一片面向对象编程的新世界, Delphi的严谨语法和快速编译吸引了众多的应用开发者,加上Borland的完美的VCL组件体系,比起MFC来方便而容易,另外,Delphi完整的数据库组件,也将数据库开发变得简单而容易,Delphi再次成为成熟的面向对象开发语言。微软当然不会放过这些,通过将MFC内置到操作系统中,微软的VC++也抢回一些市场。这也是为什么Delphi开发的应用程序编译后会比VC、VB开发的程序大的原因。
1995年,Sun的一个开发小组本来为了小型嵌入式系统开发OAK语言,结果无心插柳柳成荫,发展出了Java语言,它是一个完全摆脱了传统语言的各种负担的面向对象的语言,当然,也保留了一些非面向对象的核心(原始类型)以保证速度。现在Java也为最流行的面向对象语言之一。当然,微软同样不会放过它,擅于模仿的微软立即弄出一个C#来与之竞争,并在C#中保留了一些变种的指针(指代)以吸引传统的C开发者。关于这些语言的各自特点,这里就不一一赘述了。
2.2 数据库与数据对象化
数据库是企业级应用不可缺少的,因此,在面向对象流行的时候,数据库厂商也在进行着数据对象化的研究。这些研究在上个世纪八十年代就初现端倪。
数据库的对象化一般有两个方向:一个是在主流的关系数据库的基础上加入对象化特征,使之提供面向对象的服务,但访问语言还是基于SQL;另一个方向就是彻底抛弃关系数据库,用全新的面向对象的概念来设计数据库,这就是对象数据库ODBMS。
2.2.1 关系数据库对象化、SQL99与JDBC3.0
随着许多关系数据库厂商开始提供对象化服务,各自的接口开始互不兼容,在经历一些麻烦之后,关系数据库厂商感觉到规范化的必要,因为当初关系数据库雄霸天下时SQL92标准起了很大作用,大家可以按照统一的编程方式来访问高性能的商用数据库。
关系数据库厂商集中起来,重新将对象化服务规范起来,形成了SQL99规范,将其中的对象结构等内容规范起来,开始一个崭新的面向对象的关系数据库(ORDBMS)的历程。
JDBC3.0就是在这种情况下出台的,它将对关系数据库中的对象服务的访问API规范起来,为Java平台提供了访问ORDBMS的标准方式。当然,JDBC3.0对传统的SQL操作也进行了很多功能增强。
Oracle是一个传统的关系数据库厂商,在对象化的道路上,Oracle当然采取追加对象化特征的道路,以侵入数据对象化的市场,保持Oracle在数据库领域的领导地位。如果说Oracle7.4使Oracle走向全盛的话,从Oracle8开始,Oracle就成为关系数据库加对象类型的先驱。在Oracle8中,我们可以定义一些数据结构(Record),将普通的类型包装在其中成为数据元素,然后可以在客户端按Record结构进行访问,初步提供了面向对象的数据库服务。
2.2.2 对象数据库
对象数据库就是采用全新的面向对象概念来设计数据库的全新数据库类型。在这方面,主要以一些大学研究机构进行设计和开发,有些也形成了产品,不过由于市场方面的原因(主要是关系数据库的容易上手和市场绝对领导地位)和ODBMS先天的一些弱点(比如查询引擎很难优化),使ODBMS没有象关系数据库那样流行起来。
不过对象数据库的对象化特点还是令人割舍不下,目前还是有一些很好的产品在市场上,从商用的到免费的都用。目前在ODBMS领域占据领导地位的是Versant、FastObjects和ObjectStore等几大厂商,并且,市场份额也在逐步扩展。免费的产品包括C++编写的Ozone、纯Java的db4o等等。还有一些研究机构开发一些底层的面向对象数据库引擎,但只提供一些底层的API,不提供管理方面的功能,以及一些算法提供开放式接口,让厂商去选择和实现。比如美国威斯康新大学计算机系数据库组的SHORE引擎,就是一个非常出色的面向对象数据库引擎,现在还在积极的更新中,一些其它研究机构和数据库厂商采用它完成了自己的特别的对象数据库,比如专用于地理信息的数据库、专用于宇宙空间数据研究的数据库等等。
目前对象数据库最大的障碍是缺乏统一的规范,各个数据库厂商有各自的访问接口。对象数据库比起关系数据库来,不只是基本的几种数据类型那么简单,它还涉及继承处理、多态等一大堆面向对象特征的实现,规范化道路当然困难重重。这也是对象数据库无法普及的一个重要原因。
也有一些机构提出了一些建议的规范,比如制定Corba标准的OMG小组的一个分组ODMG提出的ODMG规范,目前已经是3.0版本,其中的OQL对象查询语言相当具有吸引力。还有一些中立的机构提出了其它的一些标准化的对象访问API,也可算是面向对象数据库的规范之一。象前面提到的FastObjects和Ozone就是符合ODMG3.0规范的。
3 Java对象映射
话说回来,在一般的开发人员眼中,数据库就是指关系数据库,因此,很多应用还是采用简单的JDBC来访问数据库。在开发的过程中,大家逐渐感觉到JDBC的局限性,比如调用复杂、容易产生资源泄漏等等,与面向对象的Java语言有一段距离,因此,很多开发小组开始思考如何将应用中的数据进行对象化建模,然后再想办法与JDBC结合起来,这就是Java数据库开发中的层出不穷的对象包装技术。
3.1 对象包装技术
3.1.1 传统包装与演变
传统包装顾名思义,就是最初出现的包装方式,很多公司都经历过这一步,产生了很多风格各异的包装方法。当然,笔者也有过还算丰富的尝试过程。
举例来说,如果我们有一个用户类:
public class User {
public int userId;
public String name;
public java.util.Date birthday;
}
我们可以将其当作一个简单的数据类,然后写一些工具方法来实现与JDBC的交互。这些方法,我们可以放到一个另外的工具类中,也可以放到User类中作为静态方法。这些方法包括简单的增、删、改、查(以Oracle为例):
public class User {
public int userId;
public String name;
public java.util.Date birthday;
public static User addUser(String name, Date birthday) throws SQLException {
Connection conn = …; //获取一个JDBC连接
PreparedStatement ps = conn.prepareStatement("…"); // 获取一个序列值来作为用户标识
ResultSet rs = ps.executeQuery();
rs.next();
User user = new User();
user.userId = rs.getInt(1); //读取序列值为新用户标识
user.name = name;
user.birthday = birthday;
ps = conn.prepareStatement("insert into …."); //插入用户数据记录的SQL
ps.setInt(1,user.id);
ps.setString(2,user.name);
ps.setDate(3,user.birthday);
ps.executeUpdate();
rs.close();
ps.close();
conn.close();
return user;
}
public static void deleteUser(int userId) throws SQLException {
Connection conn = ….;
//…
}
public static User getById(int userId) throws SQLException {
//…
}
//…
}
以上就是一个简单的数据包装的基本雏形,我们可以看到,这是一个非常简单的JDBC包装,一些代码可以模块化,以实现重用。另外,这段代码还有很大隐患,就是中途如果出现异常的话,就会使系统出现JDBC资源漏洞,因为JDBC分配的资源(conn,ps,rs等)是不能被Java虚拟机的垃圾回收机制回收的。因此,我们的addUser方法就需要改成下面的样子:
public static User addUser(String name, Date birthday) throws SQLException {
Connection conn = null;
PreparedStatement ps = null;
ResultSet rs = null;
User user = new User();
try {
conn = …; //获取一个JDBC连接
ps = conn.prepareStatement("…"); // 获取一个序列值来作为用户标识
rs = ps.executeQuery();
rs.next();
user.userId = rs.getInt(1); //读取序列值为新用户标识
user.name = name;
user.birthday = birthday;
ps = conn.prepareStatement("insert into …."); //插入用户数据记录的SQL
ps.setInt(1,user.id);
ps.setString(2,user.name);
ps.setDate(3,user.birthday);
ps.executeUpdate();
} finally {
//这里注意一定要按照创建的顺序关闭JDBC资源:
if(rs != null) try { rs.close(); } catch(SQLException ex) { ex.printStackTrace(); }
if(ps != null) try { ps.close(); } catch(SQLException ex) { ex.printStackTrace(); }
if(conn != null) try { conn.close(); } catch(SQLException ex) { ex.printStackTrace(); }
}
return user;
}
所有的数据库访问方法都必须进行这样的包装,当我们的数据类达到一定的数量后(比如十几个,几十个),这些方法占据了大量的代码,维护困难、出现BUG机会增多,并且不易排错,尤其是资源漏洞这种容易引起服务器稳定性问题的BUG。
为了保持数据类的纯洁,我们可以将JDBC操作方法集中到一个公共工具类中去完成,这样,这个工具类会非常庞大,但每个数据类会变得很简单,这种方式,可以称作是DataAccessObject模式,相当于EJB中的ValueObject,是脱离数据库细节的纯对象模型。
这些都是最基本的存储处理,在基本增删改查(这里的查指按关键字查找对象)的基础上,我们还需要进行复杂的匹配查询(SQL),这使得我们的存储处理代码进一步复杂化。简单地,我们可以写一个类似的方法:
public static Collection findBy(String sql) throws SQLException {
//… (这里获取JDBC连接)
Collection col = new Vector();
ps = conn.prepareStatement(sql);
rs = ps.executeQuery();
while(rs.next()) {
User user = new User();
user.userId = rs.getInt(1);
user.name = rs.getString(2);
user.birthday = rs.getDate(3);
col.add(user);
}
return col;
//… (同前,这里是清理JDBC资源的代码)
}
这就是一个查询接口的基本定义,查询采用的语言仍是SQL。
如果我们需要将参数从SQL串中独立出来以节省数据库的解析时间并规范化,我们还需要将查询条件作为参数传递到这个方法中去,方法的接口改为:
public static Collection findBy(String sql, Object[] params) throws SQLException {
//…
ps = conn.prepareStatement(sql);
for(int i = 0; i < params.length; i++) ps.setObject(i+1,params[i]);
//…
}
调用的时候sql参数中会包含一些"?"号,如:
select ID,NAME,BIRTHDAY from USER where … = ? and … > ? and …
当然,也有一些开发团队喜欢将所有可能的查询都写死成一个个的专用查询方法,在其中完成对应的SQL操作,这一点类似于EJBQL,只不过是将EJBQL中容器实现的功能通过手工编码来实现。这样做使得查询受到限制,但可以提供更保险的接口。
还有一些开发人员看到每个类中写这样一些查询方法使得这个类的代码变得庞大,维护麻烦,便将所有的查询方法放到一个公共的工具类中去,只是在方法中再加入一个参数:Class cls来表示需要查询哪个对象,使得每个数据类变得紧凑一些。当然,这样的结果是那个公共类变得异常庞大,谁维护谁倒霉,可以说是牺牲一人,幸福团队。不过如果这个人心理素质不够好、压力承受能力不强的话,一些对数据类的改动可能会受到他的阻碍,这时候就是"一夫当关,万夫莫开"。
现在,我们已经实现了基本对象的包装,现在才能开始考虑更多的问题。首先,我们可能会从规范化的角度出发,给每一个属性加上读写访问器getter/setter,在前面的User类中,可能我们会将基本属性部分写为:
public class User {
private int userId;
private String name;
private Date birthday;
//以下是针对以上属性的getter/setter,一般可以用IDE工具生成
public int getUserId() { return userId; }
public void setUserId(int value) { userId = value; }
public String getName() { return name; }
public void setName(String value) { name = value; }
public Date getBirthday() { return birthday; }
public void setBirthday(Date value) { birthday = value};
//…
}
这样,一个比较规范的数据类包装就算完成了。
另外,我们知道,面向对象概念中,一个属性可以是另一个对象,也就是说,对象之间是存在着引用关系的,这种关系还分为一对一、一对多、多对多等几种情况。从一个既定对象出发,其某个属性可以是另一个对象,也可以是包含另一组对象的集合。那么,在我们的数据包装里面,当然最好也能方便地处理对象之间的关系。假定现在我们又有一个数据类Group:
public class Group {
public int grouId;
public String groupName;
public Set users; //set of User
}
这里为了简单表明含义,暂不采用getter/setter。
而User对所属的Group有一个引用:
public Group belongTo;
在这里,User.belongTo和Group.users就是一个一对多的关系。
在我们的数据类中,如何才能实现数据库的存取呢?就算是不考虑Group.users这个反向关系,光是User.belongTo就是一件头疼的事。如果我们在取出一个User对象时同时将其Group对象也取出来,可以保证不会在访问某个用户的组时得到一个null。不过这样有几个缺点:
1. 数据类的存取处理(JDBC)变得复杂,需要执行很多SQL读取才行,有时候只需要访问User的基本属性时浪费时间和资源;尤其是对集合型属性的预读取,更加可怕
2. 如果按这个逻辑,双向的关系处理变得危险,很容易陷入死循环,如果要避免,必须在类代码中加入一些特别的机制,也是很麻烦的事
3. 如果对象之间的关系是更复杂的情况下,比如三个、四个对象之间互相关联,那就是一场噩梦,对代码的编写和维护都异常艰难
于是,很多开发人员自然而然地退后一步,在User类中只保留一个groupId,并不保存Group对象,这样,可以将User.belongTo属性变成int类型,而另外写一个getter方法:
public Group getBelongTo() {
return Group.findById(belongTo);
}
而在Group类中,干脆将users属性去掉,只保留一个方法:
public Set getUsers() {
return new HashSet(User.findBy("select … from USER where BELONG_TO=?",new Object[]{ new Integer(groupId) }));
}
也许细心一点的读者已经看出来了,这里的几个方法都没有将SQLException捕捉,也没有在方法中声明,也就是说是有语法错误的,因为SQLException不是一个RuntimeException。不错,确实是这样,不过我们这里为了简单明了起见,省掉这些处理,以更直接清楚地表达意思。
这样,实际上,我们的对象关系包装已经名存实亡,在类的内部有很多用于访问所引用对象的复杂代码,这些已经违背了我们将对象关系保持的初衷。有些开发人员甚至不在类中保留访问关系对象的方法,而是在客户调用时再去访问另一对象类的读取方法,如:
…
User user = User.getById(…);
//对user对象进行一些访问,如显示其姓名等等
Group group = Group.findById(user.belongTo);
//对group对象进行一些访问,如显示组名等等
…
在这样的代码里,实际上我们已经从根本上退回了关系数据库的出发点,这与直接访问数据表有多大区别呢?只不过是在表上面套了一层貌似面向对象的皮而已。不幸的是,这种方式还存在于很多应用之中。
从前面提到的这些面向对象包装的细节问题,我们可以看到这种传统包装方式的一些主要的缺陷:
3.1.1.1 数据库命名与对象设计命名的一致性问题
很多时候,我们兴致勃勃地设计好一个类图,并且经过评审之后,开始对它进行数据库包装,这时候,发现有些属性名在具体的数据库中与该数据库的关键字冲突,不能用作表名或字段名,必须在数据表设计时采用另外的名称,或者很麻烦地加上引号来使用。于是,写数据库处理代码的人有意见了,他必须很清楚地记得一个属性在类中是什么属性名,在表中又是什么字段名,在编写的类逐渐增多后,尤其是一个类经过历次变动之后,或者经过很长时间又需要改动,并去处理这些JDBC代码时,代码维护人员简单要发疯了!
3.1.1.2 对象的查询仍局限于SQL
这一点也是这种简单的包装方法最不能摆脱关系数据库的地方。上面也已经说过,一些命名冲突带来了很多维护工作量,代码中必须还保留数据表的命名体系,而不是对象类的命名体系。这将使调用人员仍需要清楚记得两套命名体系,无论时间经过多久,或者开发人员是否有流动。
此外,对于普遍需要用到的连表查询,也给应用开发带来困难,这些地方仍不能突破SQL的限制。
3.1.1.3 SQL资源占用多
在处理对象集合访问或者处理集合类型属性时,往往我们只能在同一个Connection中处理一切事务,这就要求一次性地将集合中的对象全部读入,如果集合很大的话,容易造成数据库拥塞,使得性能大打折扣,适得其反。这方面的优化也是很多开发人员一直在努力改进的。
3.1.1.4 对象关系处理复杂
前面提过,对象之间的关系处理上,普通的包装技术是一种表面上的处理,在访问时调用者仍需要用大量的代码进行处理,并且,这还只是读取,在写入关系属性时会有更多的细节问题需要处理。
3.1.1.5 面向对象特色只能应用一小部分
面向对象的包装到前面所说的为止,都还只是很基本的处理,而面向对象的精华:继承和多态,在这里得不到任何帮助。我们放弃了很多合理的设计来迁就数据库的包装。
以上就是基本数据包装的主要缺陷,还有更多的小问题也需要额外的处理。
不过,有责任心的开发人员当然不会就此善罢甘休,他们冥思苦想,可能会用更好的方式来实现对象之间关系的处理,而且还会加入一些延迟访问的机制,将对象之间的引用在需要用的时候才去连接数据库取数据,另外,在类的继承上,他们也试图去在数据库包装上得到体现。
值得感谢的是,一些富有经验的团队在经历若干改进之后,毫不吝惜将他们的成果贡献出来,并将其独立化,成为可复用的组件。这也就是后来出现的对象包装产品,包括商用的和免费的。
3.1.2 产品化包装中间件
面向对象的包装产品化后,逐步推广开来,称作关系/对象映射:O/R Mapping。并且,在产品化的过程中,产品提供者在产品中逐渐提供了更多的功能。先是一些自动命名转换器得以应用,将客户代码中的类名、属性名在内部处理的时候自动地转换到对应的数据库结构命名空间上,这样,应用开发的时候不再关心具体的数据库结构,不再需要记忆不同地命名体系,只需要一心一意地根据类图来进行开发。在此之后,这些O/R Mapping产品的开发团队又向前迈了一大步:引入了词法分析器,提供面向对象的查询语言!很多对象关系的查询使应用开发变得非常方便,使O/R Mapping产品上了一个新的台阶。
3.1.2.1 TopLink
TopLink是一个非常早期的产品,最初面向C++,后来也实现了Java的映射。TopLink性能优异,功能强大,并且提供了独特的查询过滤器机制,以及对关系的处理和查询都非常有效,于是,TopLink逐渐从商用化O/R Mapping产品中胜出,成为市场上的最出色的映射产品。也正因为这一点,最大的关系数据库厂商Oracle将其收购,成为提供最强数据库和最强对象映射中间件的厂商。
3.1.2.2 Castor、Hibernate
TopLink虽然强大,但太强大的东西免不了得意忘形,TopLink开始将用户锁死到自己的产品上,查询方式是最突出的。它的查询体系含有很多别扭的概念(在我看来是如此),但为达到一般O/R产品不能达到的功能,开发者只能接受这些。慢慢地,也产生积怨,再加上其高昂的价格,让很多新老用户望而却步。于是,免费的产品开始崛起。
免费的O/R Mapping工具有很多种,这里只提其中最有影响力的两种:Castor和Hibernate。
Castor是Exolab组织开发的面向Java的包装工具,它最大的特色就是实现了大部分的ODMG OQL规范,在查询上,可以象完全使用一个对象数据库一样类图进行查询(后面会有介绍)。它的原理是通过Java反射API去实现属性的设置和读取。不过由于各种原因,Castor后来的版本更新越来越慢,最终停步在1.0之前,成为至今未出到1.0正式版的O/R Mapping产品。不管怎么样,它还是一个相当不错的产品。
Hibernate是一个现在很火热的O/R Mapping产品,目前已经出到2.0版,它功能一样强大,同样使用Java反射API进行对象的设置,但它的查询语言就是一套比较独特的体系,这一点有点类似TopLink,但Hibernate更具有亲和力,对关系的查询更方便,只不过比起Castor来,在方便性和规范性上还是稍逊一筹。就目前状况而言,Hibernate的用户量和技术支持要强一些。
3.2 面向对象的数据库查询
在对数据库进行面向对象研究的过程中,软件世界的开发人员和设计人员们发现:对数据库能够进行对象化的查询,才是对数据库进行彻底的面向对象化。这体现在我们使用一种全新的数据库查询语言,能够很简洁易懂地对数据库中的对象进行查询。一个典型的例子如下:
假设我们已经有前面提到的两个数据类:User和Group,它们之间有一对多的关系:User.belongTo和Group.users。在数据库中已经存在很多这两个类的实例,以及相互之间的关系。我们可以使用下面的对象式查询语言来查询符合条件的User对象:
select * from User where User.belongTo.name='GROUP1'
或者
select userId,name from User where User.belongTo.name='GROUP2'
等等。从中我们可以看出,通过使用面向对象中的成员属性指定符".",可以让我们达到SQL中的连表的效果,实际上,第一个句查询的SQL等价版本是:
select a.* from USER a, GROUP b
where a.BELONG_TO = b.GROUP_ID
and b.NAME = 'GROUP1'
由此可见,对象式的查询语言,比起实现同样功能的SQL语言来说,简单了很多,意义也更明确,更符合使用者的思维习惯。在类图比较复杂、查询涉及的类又比较多的时候,这种新型的查询语言体现出绝对的优势。
3.2.1 ODMG,OQL,Java Binding
在面向对象式查询语言的研究过程中,开发人员们逐渐实现了相似的查询语言,然后互想取长补短,最终在ODMG组织(www.odmg.org)的统一下,形成了规范化的语言:ODMG OQL,这是一种完全面向对象的数据库查询语言,语法与前面提到的类似,不过考虑了更广泛的情况,语句更加简洁而严谨。
OQL并不是针对某种语言的,它可以被应用到很多种开发语言中,它不象SQL那样只是纯字符串式的查询语句,因为面向对象查询中还必须提供相关类的信息,所以OQL需要在编程语言中实现一些特定的API。
目前,ODMG的OQL已经被规范化地应用到SmallTalk、Java、C++这些面向对象的程序设计语言当中,在ODMG的规范中,这几个模块被称作SmallTalk Binding、Java Binding和C++ Binding。
不过,由于历史原因,ODMG并没有象想象中地那样得到广泛应用,现有的十几个面向对象数据库中,采用ODMG OQL规范的少之又少,目前也只有FastObjeccts、Ozone这些产品采纳了这个规范,而象Versant这样的大厂商还没有采取OQL来查询数据库,而是自己定义了自己的一套API,称作VQL(Versant Query Lanaguage)。
在JDO之前的O/R Mapping产品中,也有一些产品使用OQL(有时候是其子集),比如Castor、Apache的Jakarta小组开发的OJB等等。
3.2.2 第三方协议
软件世界是一个多姿多彩的世界,总有那么一些好事之士不断地冒出新奇的想法。还有一些开发面向对象数据库的组织制定了自己的一套对象式数据库查询语言,自己的规范。
不过这些规范相对来说,影响力小得多。比起ODMG来,可以说应用范围太小,更不用说与SQL这样广泛应用的标准进行比较了。
3.2.3 EJBQL
Sun为了使Java应用在企业级数据库应用中,不遗余力地推广J2EE,在2001年的时候,推出了EJB2.0规范,其中包含了富有特色的面向CMP方式的EntityBean的查询语言:EJBQL,功能类似于ODMG OQL,只不过只能在EJB发布时静态地存在于应用描述符中,不能在程序中动态地使用。这是EJBQL最大的弱点,也许EJB3.0规范会将其动态化,但到了那一天,世界多半已经不是现在的样子了。
3.2.4 JDO
JDO中有最近规定的一个对象式查询语言规范,称作JDOQL,比起OQL来,JDOQL将查询语言中的很多元素与Java语言紧密地结合在一起,有的人觉得麻烦,有些人觉得规范,评论各不相同。从笔者个人的角度来看,这样有利于没写过数据库应用、没用过SQL的新手很快地习惯JDOQL,但实际上,有多少人会在没写过SQL,没了解过关系数据库的情况下去用JDO写数据库应用呢?毕竟市场说明了一切。个人认为,JDO中对数据库对象的查询多少显得有些累赘,如果能更简化一点,那将更吸引使用传统SQL的开发人员。
4 JDO历程与主要产品
说起JDO,其来由还有一段特殊的背景。Java语言在JDK1.1达到比较实用的目的后,企业级数据库应用也正是软件开发市场中的重要组成部分,Sun看到这一点后,也希望通过Java这个强大的武器在数据库开发市场攻占市场份额。JDK1.2推出后,Sun同时推出了面向企业应用的EJB,对基于java的中间件服务器框架进行了规范化定义,这就是J2EE。不过在JDK1.2时,Java的速度还是不能与传统的C/C++和Delphi这样一些应用开发语言相比。为了防止业界对Java的激情因此而消退,Sun宣布将在JDK中加入强大的虚拟机技术HotSpot,其中包含更先进的垃圾收集算法和更优化的Java字节代码再编译技术(相当于JIT,Java即时编译技术)。HotSpot引起了Java关注者的极大兴趣,但Sun的HotSpot一拖再拖,最后包含在JDK1.3中出来时,性能也没有象预期的那样好,比起C++编译生成的代码来还是有一段距离。
这个时候,大家开始对Sun心存怀疑,而Sun深知这一点,于是将公众的注意力赶紧转移到EJB上,从EJB1.0到EJB1.1,再到EJB2.0,业界又开始关注J2EE中间件技术上来。很快,开发人同发现用EJB来编写数据库应用还是有很大的难度,虽然在分布式技术上EJB确实有很大的价值。在这个时候,Sun决定推出JDO技术作为轻量级的Java数据库访问规范,而这一点也受到很多传统O/R Mapping市场的欢迎。为了与传统O/R Mapping有所区别,Sun一开始就高姿态地将JDO定位成不只是面向关系数据库的Java规范,而是针对所有的存储技术,包括面向对象数据库和其它类型的存储体系(如文件),就象EJB EntityBean一样,虽然。就笔者的角度,这个做法使第一版的JDO抛弃了很多传统O/R Mapping提供的面向关系数据库的功能,可以算是一个失策。
4.1 规范提出、JSR
JDO最早是由Sun召集众多的O/R Mapping开发团队集中起来共同提出的,首先是通过会议确定了JDO需要包括的内容,然后正式提出一个Java规范请求(JSR-12),正式开始了JDO规范的制定。下面是主要的进展里程碑。
JSR #000012 approved in July 1999
1999-8组建的专家小组:包括Sun、Apple、BEA、IBM、Oracle、SAP、WebGain等
2000-5 完成公开评论草案
2000-6 在JavaOne上引入
2001-3 最终草案0.93
2001-5 最终草案0.96公布
2001-6 在JavaOne上启动
2001-11 最终草案0.98
2002-4 1.0版正式公布
2002-8 1.0.1修正版
2003-8 2.0规范启动
…
4.2 Oracle与JDO
作为JDO专家组的重要成员,同时作为最大的关系数据库厂商的Oracle地位显然非同一般。在JDO规范中,Oracle也可说是立下汗马功劳,很多API的形成,Oracle都提供了很重要的参考意见,最终的投票Oracle也是毫不犹豫。
可是,世间的事总是变化莫测的,就在JDO1.0快出台之时,Oracle收购了TopLink,这一点使Oracle的身份变得特殊而复杂。TopLink是一个商业产品,是以商业利益为目标的,而Oracle也是追求利益最大化的典型商家,这一点与JDO的开放精神背道而驰。因此,我们看到后期Oracle对JDO的不积极态度,甚至在前一阵的JavaOne大会上有人从Oracle的角度非正式地攻击JDO。
4.3 主要产品以及各自特点
在JDO规范制定的同时,出现了几个主要的JDO产品,有美国的基于对象数据库的FastObjects j1、法国的支持Versant对象数据库、文件数据库、主流RDBMS的LiDO、南非的JDOGenie、德国的JRelay等等,这些都是很不错的JDO产品。下面列举一下我对主要的几个产品的印象:
LiDO(法国LibeLis公司)
我对JDO的认识主要是通过LiDO这个产品,它在2002年3月的一份图文并茂的教程中简要解说了JDO的使用和优点。这个教程可以在这里下载:http://www.objectweb.org/conference/JDO.pdf。LiDO的特色是大而全,支持文件型数据库、RDBMS、ODBMS,甚至是XML数据库。不过配置较麻烦。最新版本是2.0RC。
KodoJDO(美国SolarMetrics公司)
Kodo是JDO的中流砥柱之一,在JDO1.0还未最后通过的时候,它就是一个比较成熟的产品了,其特点是注重性能和稳定性,目前最新版本是2.5.0,是客户最多的产品。
JDOGenie(南非HemSphere公司)
这是目前我最推荐的产品,最新版本是1.4.7,性能也不错,稳定性还有待验证,但它有一个最大的特点:集成性好,最易学,其公司的CTO David Tinker也是一个善解人意的年轻人,采纳了很多网友的意见对产品进行改进,主要是在配置上非常方便,有一个专门的图形界面工具,可以进行配置、数据库生成、对象查询等等很实用的功能。
JRelay(德国ObjectIndustries公司)
这也是一个出现得比较早的产品,也有一个GUI工具用于配置,曾几何时,这个工具还是相对很方便的,但一年多过去了,好象没什么进展,最新版本是2.0,我试过一段时间,后来就没有再跟进了。
FrontierSuite for JDO (美国ObjectFrontier)
这个产品与JRelay、Kodo一起,可算是早期的JDO三个主要产品,它支持正向开发和反向开发(Reverse Engineer)。它的特色是反向工程(从表结构生成数据类)比较方便,与UML的结合也很强,不过真正运行起来的时候,配置复杂。
TJDO(一群跨国界的有志之士)
这是一个在Sun提供的参考产品(Reference Implementation)的基础上加入一些扩展功能而形成的一个免费产品,目前最新版本是2.0beta3,不过进展也缓慢,这个版本已经出现好几个月了没有进一步的更新。
5 目前状况与未来展望
从2002年4月JDO1.0规范正式公布以来,各个产品层出不穷,从商业到免费的层次,都有不错的产品推出。象Kodo、Lido、JDOGenie等产品都已经比较成熟,可以考虑投入开发使用。在2002年8月,JDO又推出1.0.1修正版,修正了1.0版规范中的一些文字错误,以及轻微地改进了部分异常定义,不过改动都不大。从现在的情形来看,除了Kodo有一些大学的项目用到外,好象还没看到多少使用JDO作开发的应用。
JDO毕竟是一个新技术,从概念上到实际应用上对其掌握的用户还不多,而这些产品在配置、使用上的方便性易用性还有待大幅度改进,因此,真正用JDO来开发项目的用户还廖廖无几,至少我还不知道有哪些项目使用了JDO。我自己也尝试使用JDO来开发项目,但由于一些JDO1.0版本中还不够完善的一些硬伤(比如不支持关系数据库统计功能),使我对JDO仍处于观望阶段。
在八月中旬进行的JDO2.0规划会议中,来自各国的各个JDO产品厂商、JDO技术咨询公司、JDO研究机构的代表汇聚一堂,将各自收集到的用户对JDO2.0的需求总结起来,提出一个个的新的议题,并且确定了每个议题的JDO规范撰写负责人,比如高级fetchGroup特性由目前实现得最好的JDOGenie的CTO David Tinker负责,关于managed-relationship特性由Kodo的产品总监负责,用户要求最多的JDO对象与PersistenceManager的脱钩/重挂钩特性由Sun的Craig Russell亲自操刀,等等。
最具有吸引力的JDO2.0议题,笔者个人认为是专门为关系数据库设立的子规范JDO/R,这也是我一直以来最关心的,这将使目前JDBC的开发将可以被JDO完全取代,并且保证开发过程保持面向对象的特色。还有一些将一个类映射到多个表之类的特性也在规范化的列表上,这将有利于DBA在不影响应用开发的前提下根据需要更改数据表结构,实现更好的性能。类似的新特性还有很多,粗略地看,这些都规范化起来后,真不知道各个厂商还能做什么样的扩展特性,也许以后厂商之间拼的只能是技术支持服务和产品性能了,当然,最后还有价格的竞争。
说了这么多,我想大家关心的还是JDO2.0到底什么时候能出来,我们什么时候可以用上它,什么时候有中文版产品,价格到底如何。这些问题目前笔者还无法一一回答,只能根据笔者所掌握的信息初步解释一下。据前几天的JDO2.0启动大会上David Jordan的预期,JDO2.0正式版将在18个月后正式完成。那正式完成后厂商什么时候才能提供符合规范的产品呢?这个问题不用担心,因为规范在制定的过程中会不断地公布公众预览版(Public Review),这样,厂商可以先实现其中的功能,等规范正式完成后,也不会有太大的变化,厂商也不会需要太多时间来跟进最终规范。所以,我估计一年之后,我们就可以在JDO2.0上进行开发了。至于价格如何,1.0规范的产品初步印象是每个开发人员需要一个License,一个License大概2000美元。如果JDO2.0产品的价格还保持这么贵的话(至少对中国用户来说),我们也还有很多免费(如JPOX,TJDO)或半免费(如JCredo)产品可以选择。最后一个关于中文版的问题,有待于厂商对中国市场的考察,或者看看是否有国内的JDO产品了。不过,在JDO2.0规范制定的同时,我们可以先集众人之力将其普及,使广大的使用Java语言进行数据库开发的开发人员都来认识JDO,了解JDO,甚至将其用到项目开发之中。
也许,JDO的目标已经吸引了Java世界以外的人,微软发现了这一点,也立即计划在.NET体系中加入一个仿照JDO的中间件,具体是采用ObjectStore的产品,ObjectStore是一个同时做JDO和.NET-DO(姑且使用这个名称)的公司。
在这里,笔者可以大胆地预测,在未来的一两年内,JDO将在Java世界大放光彩!
5.1 一点花絮
在2003年6月举行的JavaOne大会上,JDO备受瞩目,很多开发者或开发组织对其产生了极大的兴趣,在各大网站媒体上也频频曝光,大多对其评价不错,当然,也有一些负面的评价。
不过,网站上的报道也不可尽信,比如服务器领域的权威网站TheServerSide.com上介绍JavaOne大会第二天关于JDO的一次会议的报道就有误导之嫌,从报道的内容来看,好象会议用来答疑与交流的的最后五分钟全被一个好事者占据,这个人拼命鼓吹Oracle的TopLink解决方案和一些易用性的改进,从而攻击JDO的市场前景。后来,我就这一信息与参加会议的JDO专家组的David Jordan交流时,他纠正了我的错误理解,实际情形是:那个好事者是一个积极的JDO拥护者,他花了超过五分钟时间向大家揭露Oracle的TopLink改进实际上也是抄袭JDO的概念而进行的一些API改动!不过有一点是相同的,这个好事者占据了大家的提问时间,使这次会议在意犹未尽中结束。