使用 DODS 进行数据建模

作者:Reuven M. Lerner

关系数据库构成了大多数严肃的 Web 应用程序的骨干,因为它们使数据存储和检索变得容易、安全和灵活。这种设置通常运行良好,直到开发人员开始使用对象编写程序,而对象具有完全不同的范例。我们是否有可能弥合对象世界和关系世界之间的差距?

实际上,存在许多将关系数据库映射到对象和方法的方法,并且大多数程序员都发现自己在编写这样的系统。正如我们上个月所见,Perl 程序员可以从 Alzabo 获得一些帮助,Alzabo 是一个模块,可以帮助程序员创建表,然后提供基于方法的接口来操作它们。

本月,我们将研究 DODS(数据对象设计工作室),这是一个在精神上类似于 Alzabo 的工具,但它针对的是 Java 程序员。DODS 是 Enhydra 的核心部分,其即将推出的版本(Enhydra Enterprise)有望成为第一个实现 J2EE(Java 2,企业版)的开源应用服务器。

目前,Enhydra Enterprise 仍处于 Beta 测试阶段,虽然 DODS 支持似乎自第一个版本以来有所改进,但我从 Lutris 的 Enhydra 布道者 David Young 那里得知,Enhydra 3.x 的 DODS 更稳定。为了让我更容易尝试,Lutris 向我发送了一份 EAS(Enhydra 应用服务)的副本,这是他们商业支持和增强的 Enhydra 版本。

我不完全确定 EAS 和开源 Enhydra 服务器之间有什么区别;enhydra.org 指出 EAS 是“基于”Enhydra 的,但 EAS 和购买 Enhydra 副本之间的区别根本不明显。我将假设我安装的 EAS 副本在很大程度上类似于 Enhydra 3.x,但这可能不是一个准确的假设。

DODS 概述

DODS,就像我们上个月讨论的 Perl Alzabo 系统一样,有两个目标:帮助使用高端工具设计数据库,并提供一组对象和方法来处理该数据库。虽然 Alzabo 是一个服务器端 Web 应用程序,但 DODS 是一个用 Java 编写的客户端应用程序,可让您定义和编辑数据库定义。

DODS 的目标是创建描述相同关系数据库的并行 SQL 定义和 Java 类。然后,您将 SQL 定义输入到数据库中,并使用 Java 类来访问它们。

此外,DODS 旨在与不同的数据库一起工作;目前,它与 PostgreSQL、MySQL、Sybase 和 Oracle 一起工作,但将来可能会与更多数据库一起工作。由于实际的 SQL 查询是由对象中间件层编写的,这意味着您可以将 Enhydra 程序从一个数据库移动到另一个数据库,而无需重写它们。当然,在现实中,事情要复杂得多。例如,Enhydra 对 PostgreSQL 的支持不是很令人印象深刻,它忽略了 SERIAL 数据类型(它实际上只是一个序列),并且不了解引用完整性约束,例如外键。尽管如此,这个目标是令人钦佩的,我期待看到 Enhydra 4.x 如何处理这个问题。随着时间的推移,我期望 DODS 将继续改进其对不同数据库的支持,为各个 SQL 方言创建适当的查询。

创建 XMLC 文档

让我们创建一个使用 Enhydra 和 DODS 的简单 Web/数据库应用程序,以演示它的工作原理。在我的示例中,我将使用 PostgreSQL,这既是因为它是一个优秀的开源数据库,也是因为 DODS 支持它。与过去几个月的情况一样,我们的示例将是一个简单的 Weblog(或博客)应用程序,它以倒序时间顺序显示数据库表中的条目。编写这样的程序并不特别困难,这使其更具吸引力,可以作为 DODS 和 Enhydra 的测试。

我们的第一站将是 Enhydra appwizard,它为我们的基本 Enhydra 应用程序创建目录和骨架文件。appwizard 位于 $ENHYDRA/bin 中,其中 ENHYDRA 是一个环境变量,用于命名 Enhydra 安装的位置。(当我使用 RPM 从 CD 为我的 Red Hat Linux 机器安装它时,ENHYDRA 设置为 /usr/local/lutris-enhydra3.5.2。)

在第一个 appwizard 屏幕上,我被允许在标准 Web 应用程序和 Enhydra 超级 Servlet 之间进行选择。我选择了后者。在下一个屏幕上,我选择了一个 HTML 项目(而不是无线 WML 项目),将项目命名为“blog”,并将其放在 il.co.lerner 包类中。我还接受了 Enhydra 应用程序的默认主目录 ~/enhydraApps/。我选择不将版权与我的源代码关联,然后单击“完成”,这在 ~/enhydraApps/blog 中创建了 18 个新文件。

现在我已经为我的应用程序创建了骨架,我将修改 Enhydra 附带的默认欢迎页面。我们将分两部分完成此操作;首先,我们将修改 HTML 文件 Welcome.html,我的计算机将其放置在 ~/enhydraApps/blog/src/il/co/lerner/presentation/Welcome.html 中。

请注意,此文件不仅包含 HTML,还包含将由 XMLC 处理的标签(请参阅 2001 年 8 月《Linux Journal》中的“At the Forge”专栏,了解这意味着什么)。我们将更改它以显示来自我们 Weblog 的最新信息,而不是标准页面,如列表 1 所示。XMLC 文档和标准 HTML 页面之间的唯一区别是我们使用带有 id 属性的 <span> 标签标记我们想要修改的部分。例如

<p><b><span id="date">Date</span><b></p>
<p><span id="text">Text</span></p>

列表 1. Welcome.html,我们将用于显示我们的 Weblog 的 XMLC 输入文件

如果我们在 Web 浏览器中按字面意思显示此文件,我们将看到单词“Date”和“Text”。但是用户不会直接检索此 HTML 文档。相反,XMLC 会将我们的文档转换为 Java 类。然后,我们将使用 WelcomePresentation 类创建文档的实例,使用自动生成的方法来修改日期和文本部分。

使用 DODS

但是,WelcomePresentation 将使用从关系数据库表中检索的信息填充日期和文本部分。在我们继续之前,我们将必须创建表,并使用一些信息填充它。

这就是 DODS 发挥作用的地方。DODS 程序,它位于 $ENHYDRA/bin/dods 中,是另一个用 Java 编写的图形客户端程序。在使用 DODS 时,我们必须记住我们正在使用两种不同范例之间的桥梁,并且术语可能经常有点奇怪。

DODS 从一个包开始,该包是我们将创建的所有表和其他属性的容器。默认情况下,正如您从初始 DODS 窗口中看到的那样,包名为 root;我通过单击 root 文件夹并从“编辑”菜单中选择“包”将其更改为 blog。

我们将创建一个数据库表(BlogEntries),其中包含两个属性:日期和内容。(这些与我们的 Welcome.html 版本中的两个 id 标签匹配。)我们首先向 BlogEntries 添加一个新表,使用“插入”菜单,选择“数据对象”并将其命名为 BlogEntries。

接下来,我们必须向我们的表添加两个字段(date 和 text)。为了做到这一点,单击 DODS 窗口左上角的 BlogEntries 名称,并使用“插入”菜单添加每个新属性。我们的两个属性都将是“varchar”类型,这意味着我们将把它们都视为文本。虽然这对于我们的目的来说可以完美地工作,但不幸的是,DODS 不支持 PostgreSQL TIMESTAMP 数据类型,这是一种允许我们对日期和时间执行各种巧妙和复杂操作的工具。因此,我们将以纯文本格式存储日期,知道我们可以使用 ORDER BY 按顺序检索它们,仅此而已。

因为我们想要确保我们的 Web/数据库应用程序的最大速度,并且因为我们插入文本的频率远低于我们检索文本的频率,所以我们将告诉 DODS 使我们的两个字段都可索引和可查询。前者将修改 SQL 定义,确保为属性创建索引。后者将创建其他方法,使我们能够根据此列检索信息。

最后,我们从“数据库”菜单中选择 PostgreSQL,告诉 DODS 创建 PostgreSQL 样式的 SQL,而不是任何其他数据库。

一旦我们使用 DODS 完成创建表,我们将其导出到 DOML(使用“文件另存为”),这是一种描述我们的表的 XML 格式,可用于生成 SQL 和 Java。确保将 DOML 文件保存到项目和包中的源目录中;因此,我将其放在 blog/src/il/co/lerner 中。一旦我们的 DOML 文件完成(请参阅列表 2),我们可以使用“文件”菜单中的 build all 命令将其转换为 Java 和 SQL。构建所有内容会在数据目录中创建许多文件,因此当询问您要构建到哪里时,请选择您放置 blog.doml 文件的 data 目录。将弹出一个窗口,描述 DODS 在每个时刻正在做什么。如果一切顺利,您可以退出 DODS。

列表 2. blog.doml,DODS 自动生成的文件

创建数据库

从 DODS 运行 build all 将 DOML 文件转换为数据目录下的 一组文件。此目录现在不仅包含 Makefile(以前为空),还包含 blog 子目录,其中现在存在以下四个 Java 类:BlogEntriesDO,一个类似于 Enhydra 的演示对象的数据对象类,用于显示信息;BlogEntriesDataStruct,它实际上包含我们表中的数据;BlogEntriesDOI,BlogEntriesDO 类的接口;以及 BlogEntriesQuery,它允许我们查询我们描述为可查询的字段。

除了创建的 Java 源代码之外,我们还有几个包含 SQL 的文件。特别是,data 目录包含 create_tables.sql,我们可以使用它作为输入来创建我们的数据库。

为此,我们使用 CREATEDB 命令,该命令通常由授权的 PostgreSQL 用户从 UNIX shell 执行。(这不一定意味着 root 用户;请查看您的 PostgreSQL 安装文档,了解如何创建 PostgreSQL 超级用户。)

因此,我们说

CREATEDB blog

然后我们可以使用以下命令以交互方式查询该数据库

psql blog
要使用自动生成的 DODS 脚本创建我们的表,我们使用 psql 中的 \i 命令
\i /home/reuven/enhydraApps/blog/src/il/co/lerner/ data/create_tables.sql
您应该看到几个 CREATE 消息,然后再次看到 psql 提示符。使用 \d 命令,我们看到 DODS 实际上没有创建一个名为 BlogEntries 的表。相反,它创建了两个表,一个名为 objectid,另一个(主)表名为 newdbtable,这是默认名称。objectid 表代替 PostgreSQL 序列,这证明了这种通用工具的一种局限性,并且有一个列“next”指示下一个 ID 是哪个。因此,我们将信息插入到 newdbtable 表中,每次这样做时都会向 objectid 添加新行。

让我们向我们的表中添加几个项目,以便我们随后能够检索它们

INSERT INTO newdbtable (entrydate, text, objectid, objectversion)
VALUES (NOW(), 'First blog entry', 1, 1);
INSERT INTO objectid ( next ) VALUES ( '2' );
INSERT INTO newdbtable (entrydate, text, objectid, objectversion)
VALUES (NOW(), 'Second blog entry', 2, 1);
INSERT INTO objectid ( next ) VALUES ( '3' );

现在我们已经插入了两个博客条目,我们可以退出 psql 并继续修改我们的 Java 类。

将它们整合在一起

我们的 WelcomePresentation.java 类是大部分魔法发生的地方。它创建 Welcome.html 和我们的数据库对象的实例,检索查询结果,然后将这些结果插入到 HTML 文件中。

在您修改 WelcomePresentation.java 后,如列表 3 所示 [可在 ftp.linuxjournal.com/pub/lj/listings/issue91/5426.tgz 获取],从该项目的顶层目录运行 make。Enhydra 将编译您的 Java 类,仔细检查一切是否都在应有的位置,并准备好运行您的应用程序。

请注意,在列表 3 中,我们必须修改“run”方法的定义,使其返回两个新异常:NonUniqueQueryException 和 DataObjectException。这些是由我们创建的各种数据对象生成的,并且由于我们不打算捕获这些异常,因此我们必须向调用者指示我们可能会引发它们。

列表 3 使用 Enhydra QueryBuilder 使用 DODS 创建的方法创建 SQL 查询。我们首先创建 BlogEntriesQuery 的实例,它是自动创建的类之一

BlogEntriesQuery blogq = new BlogEntriesQuery();

我们想要检索到目前为止的所有行,按 entryDate 倒序排列

blogq.setQueryEntrydate("NOW()", QueryBuilder.LESS_THAN);
blogq.addOrderByEntrydate(false);
还有一些方法用于向我们的 SQL 查询添加 WHERE 子句,使我们能够创建任意复杂的 SQL 查询。

最后,我们检索匹配行的数组,每个行都由 BlogEntriesDO 对象表示

BlogEntriesDO[] blogEntries = blogq.getDOArray();

我们只打算显示最新的一个,因此我们将简单地获取数组的第一个元素。我们使用 XMLC 创建的 “welcome” 对象中的方法,将适当的文本插入到我们的文档中

welcome.setTextDate(blogEntries[0].getEntrydate());
welcome.setTextText(blogEntries[0].getText());
一旦我们修改了 WelcomePresentation.java,我们通过从我们的顶层项目目录运行 make 来创建应用程序。如果您在 Java 程序中看到任何错误,您可以更正它们并根据需要重新运行 make。

从理论上讲,您现在可以通过更改到 output 子目录并运行 ./start 来运行应用程序。但是,如果我们这样做,我们的应用程序将会失败,因为它还不知道在哪里查找 PostgreSQL .jar 文件。此外,当我们第一次使用 Enhydra(或任何应用程序)时,获取完整的调试输出非常有用,这样我们可以更快地识别和修复问题。

我们必须修改三个文件才能使事情正常工作。首先,我们需要通过添加对 PostgreSQL JDBC 驱动程序的 .jar 文件的引用来修改 $ENHYDRA/bin/multiserver。为此,我们只需修改 multiserver(它是一个调用 Java 程序的 shell 脚本),将注释“build up classpath”下的行更改为以下内容

# Where is the PostgreSQL JDBC .jar file?
PG_JDBC=/usr/share/pgsql/jdbc7.1-1.2.jar
if [ "X${CLASSPATH}" = "X" ] ; then
    NEWCP=${ENHYDRA_CLASSES}${PS}${PG_JDBC}
else
    NEWCP=${ENHYDRA_CLASSES}${PS}${CLASSPATH}${PS} ${PG_JDBC}
fi

接下来,我们修改 blog.conf。每个 Enhydra 项目都有一个配置文件,该文件告诉系统要使用哪个数据库,以及许多其他属性。在我的特定情况下,配置文件是 blog/output/conf/blog.conf,它包含我的应用程序的许多名称-值对。

我们必须修改“数据库管理器”部分的几个部分,以便指向我们的程序。您可以在列表 4 中看到完整的部分,因为它需要是这样的 [可在 ftp.linuxjournal.com/pub/lj/listings/issue91/5426.tgz 获取]。

最后,我们修改 servlet.conf。尽管不需要修改它,但我发现通过修改以下两个定义来打开 DEBUG 模式很有用

Server.LogToFile[] = EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, INFO,
DEBUG
Server.LogToStderr[] = EMERGENCY, ALERT, CRITICAL, ERROR, WARNING, INFO, DEBUG

关于 blog.conf 和 servlet.conf,最重要的事情是它们在您每次执行顶层 make 时都会重新生成。因此,一旦您以这种方式修改了它们,就永远不要再执行顶层 make。如果您这样做,您会非常后悔(就像我曾经做过的那样)。相反,从 presentation 目录中执行 make

一旦您以这种方式修改了配置,您就可以进入 ~/enhydraApps/blog/output 并运行 ./start。如果您在 servlet.conf 中激活了 DEBUG 并在 blog.conf 中激活了日志记录,您将看到服务器启动,以及相当多的调试信息。

您可以通过将 Web 浏览器指向端口 9000(Enhydra 应用程序的默认端口)来查看您的创建:http://localhost:9000/。如果一切顺利,您应该在 Web 浏览器中看到来自我们 Weblog 的输出。

结论

DODS 在提供比 SQL 更好的对象层方面优于 Alzabo。此外,它似乎提供了一组更好、更稳定的方法来创建查询和处理其结果。但是,DODS 遭受与 Alzabo 和其他关系对象映射工具相同的一些问题。一些问题包括必须学习一种新方法来执行您多年来一直在使用的所有 SQL 查询,以及可能令人沮丧地编写复杂查询,因为您必须将它们改写为方法调用。此外,像 DODS 这样的工具的通用性质意味着您将无法获得您最喜欢的数据库的特定优势。在 PostgreSQL 的情况下,DODS 似乎没有考虑外键或序列,这两者都会使表设计更可靠。

DODS 与 Enhydra 的其余部分配合良好。与 XMLC 和超级 Servlet 一样,我发现 DODS 最初有点令人不知所措和笨拙,然后变得有用和聪明。作为对事物的第一步尝试,Enhydra 中的 DODS 是弥合对象世界和关系世界之间差距的一次很好的尝试。我期待 Enhydra Enterprise 的最终版本,它无疑将进一步推动事物向前发展。

下个月,我们将研究一种越来越标准的方法,不仅可以弥合关系世界和对象世界之间的差距,还可以为我们的服务器端 Java 应用程序添加事务功能。Enterprise JavaBeans 代表 Web 应用程序的服务和数据,并且在希望能够查找对象、处理对象并将它们存储到数据库而无需过多思考的 Web 开发人员中变得越来越流行。

资源

电子邮件:reuven@lerner.co.il

Reuven M. Lerner (reuven@lerner.co.il) 拥有一家小型咨询公司,专门从事 Web 和互联网技术。他与妻子 Shira 和女儿 Atara Margalit 住在以色列的 Modi'in。您也可以在他的 ATF 主页 www.lerner.co.il/atf 上找到他。

加载 Disqus 评论