内容


面向 Java 开发人员的 db4o 指南

简介和概览

重新审视 OODBMS

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 面向 Java 开发人员的 db4o 指南

敬请期待该系列的后续内容。

此内容是该系列的一部分:面向 Java 开发人员的 db4o 指南

敬请期待该系列的后续内容。

在我出道成为程序员的时候,数据库之战似乎已完全平息。Oracle 和其他几个数据库供应商都非常支持和看好关系模型及其标准查询语言 SQL。实际上,坦率地讲,我从未将任何关系数据库的直接祖先,比如 IMS 或无处不在的平面文件,用于长期存储。客户机/服务器看起来似乎长久不衰。

之后,忽然有一天,我发现了 C++。正像许多在这个特别的时刻发现了这个特别的语言的其他人一样,它改变了我的整个编程 “世界观”。我的编程模型从基于函数和数据的变成了基于对象的。一时间,再也听不到开发人员大谈构建优雅的数据结构和 “信息隐藏” 了,现在,我们更热衷于多态封装继承 —— 一整套新的热门字眼。

与此同时,关系数据库已风光不再,一种新的数据库 —— 对象数据库 —— 成为了人们的新宠。若能再结合一种面向对象的语言,例如 C++ (或与之类似的编程新贵,Java 编程),OODBMS 真是可以堪称编程的理想王国。

但是,事情的发展并非如此。OODBMS 在 90 年代晚期达到了顶峰,随后就一直在走下坡路。原来的辉煌早已退去,剩下的只有晦涩和局限。在第二轮的数据库之战结束之时,关系数据库又成了赢家。(虽然大多数 RDBMS 供应商都或多或少地采用了对象,但这不影响大局。)

上述情况中存在的惟一问题是,开发人员对 OODBMS 的热衷一直没有衰退,db4o 的出现就很好地说明了这一点。

对象和关系

对象关系型阻抗失配 这个话题完全可以拿出来进行学术讨论,但简单说来,其本质是:对象系统与关系系统在如何处理实体之间的互动方面所采取的方式是截然不同的。表面上看,对象系统和关系系统彼此非常合适,但若深入研究,就会发现二者存在本质差异。

首先,对象具有身份的隐式性质(其表征是隐藏/隐式的 this 指针或引用,它实际上是内存的一个位置),而关系则具有身份的显式性质(其表征是组成关系属性的主键)。其次,关系数据库通过隐藏数据库范围内的数据查询和其他操作的实现进行封装,而对象则在每个对象上实现新的行为(当然,模块所实现的继承都在类定义中进行指定)。另外,可能也是最有趣的是,关系模型是个封闭的模型,其中任何操作的结果都将产生一个元组集,适合作为另一个操作的输入。这就使嵌套的 SELECT 以及很多其他功能成为可能。而对象模型则无此能力,尤其是向调用程序返回 “部分对象” 这一点。对象是要么全有要么全无的,其结果就是:与 RDBMS 不同,OODBMS 不能从表或一组表返回任一、全部或部分列。

简言之,对象(用像 Java 代码、C++ 和 C# 这类语言实现)和关系(由像 SQLServer、Oracle 和 DB/2 这样的现代 RDBMS 实现)操作的方式有极大的差异。对于减少这种差异,程序员责无旁贷。

映射的作用

过去,开发人员曾试图减少对象和关系间的这种差距,尝试过的方式之一是手动映射,比如通过 JDBC 编写 SQL 语句并将结果收集进字段。对这种方式的一个合理的质疑是:是否还有更简化的方法来进行处理。开发人员大都用自动的对象关系映射实用工具或库(比如 Hibernate)来解决这个问题。

即使是通过 Hibernate(或 JPA、JDO、Castor JDO、Toplink 或任何可用的其他 ORM 工具),映射问题也无法彻底解决,它们只会转移到配置文件。而且,这种方式与要解决的问题颇有些风马牛不相及。比方说,如果您想要创建一个分层良好的继承模型,将它映射到表或一组表无疑是失败之举。若用对常规形式的违背来换取查询的性能,就会将 DBA 与开发人员在某种程度上对立起来。

可问题是很难构建一个富域模型(参见 Martin Fowler 和 Eric Evans 各自所著的书),不管是您以后想要调整它来匹配现有的数据库模式,还是想要调整数据库执行其操作的功能来支持对象模型(甚或这两者)。

但如果能不调整,岂不是更好?

进入 db4o:OODBMS 的回归

db4o 库是最近才出现在 OODBMS 领域的,它使 “纯对象存储” 的概念在新一代对象开发人员中重获新生。(他们笑称,现在不是很流行怀旧么。)为了让您对如何使用 db4o 有一个概念,特给出如下代表单个人的一个基本类:

注意:如果还尚未下载,请现在就 下载 db4o。为了更好地进行讨论(或至少编译代码),db4o 是必需的,本系列的后续文章也会用到它。

清单 1. Person 类
package com.tedneward.model;

public class Person
{
    public Person()
    { }
    public Person(String firstName, String lastName, int age)
    {
        this.firstName = firstName;
        this.lastName = lastName;
        this.age = age;
    }
    
    public String getFirstName() { return firstName; }
    public void setFirstName(String value) { firstName = value; }
    
    public String getLastName() { return lastName; }
    public void setLastName(String value) { lastName = value; }
    
    public int getAge() { return age; }
    public void setAge(int value) { age = value; }

    public String toString()
    {
        return 
            "[Person: " +
            "firstName = " + firstName + " " +
            "lastName = " + lastName + " " +
            "age = " + age + 
            "]";
    }
    
    public boolean equals(Object rhs)
    {
        if (rhs == this)
            return true;
        
        if (!(rhs instanceof Person))
            return false;
        
        Person other = (Person)rhs;
        return (this.firstName.equals(other.firstName) &&
                this.lastName.equals(other.lastName) &&
                this.age == other.age);
    }
    
    private String firstName;
    private String lastName;
    private int age;
}

在众多的类中,Person 类显得极为寻常;还很简单。但若深入探究,就不难看出这个类会呈现出非常类似于对象的有趣属性和功能,例如它可以有配偶类,也可以有子类,等等。(我在后续的专栏中会历数这些属性和功能;现在,我只侧重于进行概括介绍。)

在基于 Hibernate 的系统中,将这个 Person 类的一个实例放入数据库,需要如下几个步骤:

  1. 需要创建关系模式,向数据库描述类型。
  2. 需要创建映射文件,用这些文件将列和数据库的表映射到域模型的类和字段。
  3. 在代码中,需要通过 Hibernate 打开到数据库的连接(用 Hibernate 术语来说,就是会话),并与 Hibernate API 进行交互来存储对象和将对象取回。

上述操作在 db4o 中出奇地简单,如清单 2 所示:

清单 2. 在 db4o 内运行 INSERT
import com.db4o.*;

import com.tedneward.model.*;

public class Hellodb4o
{
    public static void main(String[] args)
        throws Exception
    {
        ObjectContainer db = null;
        try
        {
            db = Db4o.openFile("persons.data");

            Person brian = new Person("Brian", "Goetz", 39);
            
            db.set(brian);
            db.commit();
        }
        finally
        {
            if (db != null)
                db.close();
        }
    }
}

这样就行了。无需生成模式文件,无需创建映射配置,需要做的只是运行客户机程序,当运行结束时,为存储在 persons.data 中的新 “数据库” 检查本地目录。

检索所存储的 Person 在某些方面非常类似于某些对象关系型映射库的操作方式,原因是对象检索最简单的形式就是按例查询(query-by-example)。只需为 db4o 提供相同类型的一个原型对象,该对象的字段设置为想要按其查询的值,这样一来,就会返回匹配该条件的一组对象,如清单 3 所示:

清单 3. 在 db4o 内运行 INSERT(版本 1)
import com.db4o.*;

import com.tedneward.model.*;

public class Hellodb4o
{
    public static void main(String[] args)
        throws Exception
    {
        ObjectContainer db = null;
        try
        {
            db = Db4o.openFile("persons.data");

            Person brian = new Person("Brian", "Goetz", 39);
            Person jason = new Person("Jason", "Hunter", 35);
            Person clinton = new Person("Brian", "Sletten", 38);
            Person david = new Person("David", "Geary", 55);
            Person glenn = new Person("Glenn", "Vanderberg", 40);
            Person neal = new Person("Neal", "Ford", 39);
            
            db.set(brian);
            db.set(jason);
            db.set(clinton);
            db.set(david);
            db.set(glenn);
            db.set(neal);

            db.commit();
            
            // Find all the Brians
            ObjectSet brians = db.get(new Person("Brian", null, 0));
            while (brians.hasNext())
                System.out.println(brians.next());
        }
        finally
        {
            if (db != null)
                db.close();
        }
    }
}

运行上述代码,会看到检索出两个对象。

但是...

在您将我定义为狂热的推崇者之前,请允许我先列举几条反对 db4o 的言论。

db4o 几乎无法对应于现有的 Oracle、SQLServer 或 DB2!
完全正确。相比之下,db4o 更适合于 MySQL 或 HSQL,这使其对于大量项目来说已经足够。更为重要的是,开销一直是 db4o 开发人员特别关注的事情,这又让它特别适于小型的嵌入式环境。(我这里给出的示例只是一个简单的演示,和其他任何小型的演示一样,请务必清楚一点,即姑且不论它相比其他工具具有多少潜力,db4o 都会比您在这里所看到的要强大许多。)
我不能用 JDBC 在 db4o 上进行查询!
确实如此,尽管 db4o 团队曾想过创建 JDBC 驱动程序以让对象数据库能接受 SQL 语法,即一种所谓的 “关系对象映射”。(开发团队之所以没有这么做,据说是因为没有这个必要,而且性能会因此大受影响。)问题的关键是,您在实现中使用的是对象(POJO),除此之外别无他物。若不存储关系,为何还要使用 SQL 呢?
但是我的其他程序如何获得数据呢?
这要看具体情况。如果您这里所谓的 “其他程序” 指的是其他 Java 代码,那么只需在这些程序内使用 Person 类的定义并将其传递进 ObjectContainer,正如我这里所做的一样。在 OODBMS 内,类定义本身就充当模式,所以无需其他的工具来获取 Person 对象。但是如果您这里所谓的 “其他程序” 指的是其他语言的代码,那么问题就会复杂一些。

对于 db4o 不支持的语言,比如 C++ 或 Python,数据基本上是访问不到的,除非是您能从 Java 代码构建程序。db4o 适用于 C# 和其他的 .NET 语言,而其数据格式在二者之间是兼容的,这就使得 Java 对象对同样定义的 .NET 类也可用。如果您这里所谓的 “其他程序” 指的是使用 SQL 和标准调用级接口(比如 ODBC 或 JDBC)来与数据库进行交互的报告工具,那么 db4o(或与此相关的任何 OODBMS)可能未必是很好的选择。机警的读者会发现报告功能现在对 OODBMS 还不可用,但好消息是:已针对此问题发起很多产品和项目,而且 db4o 还支持 “复制(Replication)”,它允许 db4o 实例将数据从其自身的存储格式复制进 RDBMS。
但它是个文件!
在这种特殊情况下,确实如此;但正如前面所讲,db4o 在何处和如何存储数据方面十分灵活,而且还提供了一个轻量级的客户机/服务器选项。如果您所期待的是功能完善的 RDBMS 所能提供的冗余性,那么 db4o 并不能如您所愿(但其他的 OODBMS 能提供这类特性)。
但当我再次运行示例时,我会得到副本!(实际上,我每次运行该示例都会得到副本。)
这里,实际上回到了我们所讨论的第一个有趣的问题:身份,正是这一点将对象数据库和关系数据库区分开来。正如我前面所言,对象系统中的身份是通过隐式的 “this” 引用赋予的,Java 对象使用这个引用在内存中标识其自身。在对象数据库中,它被称为 OID对象标识符),该 OID 在 OODBMS 中充当主键。

当创建新对象并将其 “放” 进数据库中时,新对象并不具有与其相关的 OID,因而会收到其自身惟一的 OID 值。它会复制,就如同 RDBMS 在执行每个 INSERT 操作时生成主键所做的那样(比如 顺序计数器或自动增量字段)。换言之,就主键而言,OODBMS 与 RDBMS 相当接近,但主键本身并非传统 RDBMS(或过去习惯使用 RDBMS 的程序员)认为的那种主键。

换言之,db4o 旨在解决某些方面的问题,而不是成为解决全部持久性问题的一站式通用解决方案。实际上,这让 db4o 首轮就战胜了 OODBMS:db4o 无意向那些生产 IT 人员宣称自己是多么好的一种理念,以至于完全可以放弃他们在关系数据库上的投资。

结束语

传统的集中关系型数据库作为数据存储和操纵的首选工具的地位在短期内无法撼动。以这种数据库为基础发展起来的工具非常之多,历史也很久远,而且许多程序员也都陷在 “我们总需要数据库” 的思维模式之中,这些无疑加固了其地位。实际上,db4o 在技术上的设计和定位并不是为了挑战 RDBMS 的这一地位。

但 “面向服务” 社区迫切要求我们构建松散耦合的多层世界,当您开始将 OODBMS 放到这种环境中去审视的时候,有趣的现象就出现了:如果要实现组件(服务、层或诸如此类的东西)间真正的松散耦合,那么结果常常是某种程度的耦合只存在于服务的调用程序和该服务的公开 API(或 XML 类型,不管您如何看待它)之间。无需数据类型,无需公开对象模型,无需共享数据库 —— 本质上讲,持久性方法仅仅是一个实现细节。因此,在大量场景中可用的持久性方法的范围会显著扩大。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Information Management, Open source
ArticleID=208119
ArticleTitle=面向 Java 开发人员的 db4o 指南: 简介和概览
publish-date=04092007