使用 Java Servlet 进行数据库连接

作者:Bruce McDonald

通用网关接口 (CGI) 曾经是并且仍然是创建动态和响应式网页最常用的方法。CGI 的主要问题(源于超文本传输​​协议)是每个新的客户端请求都会导致 HTTP 守护程序派生 CGI 可执行文件的新实例。这可能会导致 Web 托管设备上相当大的资源消耗。存在许多解决方案来解决这个问题,大多数解决方案侧重于在客户端请求之间保持可执行文件的持久性。这还具有保持数据库和套接字连接等昂贵资源的开放状态的额外好处。

Java 是最新兴的技术之一,但作为 C 和 C++ 程序员,我真的很喜欢这门语言的一些特性。它的对象模型很好,它(相对)可移植,并且 Sun 和第三方提供的类库非常广泛。Servlet(一个相当奇特的名称)是 Java 对 CGI 问题的解答。Servlet 是 Java 类,由 HTTP 守护程序加载并保持驻留。当 Servlet 加载时,它通过方法调用进行初始化;此时,可以建立数据库连接并在客户端请求之间保持连接。除此之外,还有许多有用的类可以促进更复杂的服务器/浏览器交互,例如 cookie。不幸的是,Sun 提供的 Servlet 类仍然处于不稳定状态,因此,现在编写的代码可能会被未来的版本破坏。这是编程生活中的一个事实,而且由于这是一个小型应用程序,因此不太可能造成太大危害。

Apache 和 JServ

由于我的站点一直在运行无处不在的 Apache Web 服务器,因此继续使用该软件是一个简单的选择。Servlet 部分是更棘手的决定。我决定稳妥起见,选择 Apache-Java 解决方案 (http://java.apache.org/)。这不是一个成品,但看起来确实可以接受,而且如果出现任何问题,我希望能够访问源代码。替代方案包括 Live Software 的 JRun 和 IBM 的 Servlet Express。JRun 能够与包括 Apache 和 Netscape 在内的许多 Web 服务器一起工作,并且该代码在相当宽松的许可下可用。Servlet Express 更像是一个未知数——也就是说,我什至没有评估它,因为它仅适用于 Apache 1.2。

JServ 的源代码可以从 Java-Apache 站点下载,我使用的版本是 0.9.11。使用 tar 将代码解压缩到合适的目录(在我机器上的 /usr/local/lib/JServ)并仔细阅读文档。Makefile 完成了大部分魔法。Makefile 需要编辑以修改安装目录——唉,没有 autoconf。需要最新的 JDK——我使用了 Steve Byrnes 1.1.6-5,我发现它既快速又稳定。我还有 tya 1.1-3i JIT(即时编译器),它可以非常透明地为 Java 代码的执行提供超强动力,但这不是必需的。制作 JServ 代码以将 Java 源文件编译到本地目录。JServ 包中包含来自 Sun 的必需 Servlet 代码。Sun Servlet 代码(以 jar 文件形式)和 JServ 代码都需要添加到您的 CLASSPATH 变量中,以用于开发和 Apache。

现在轮到 Apache 了。获取一个不错的、最新的副本——我有 1.3.1,我发现它是一个杰作——并将源代码解压缩到另一个目录中。构建外部 Apache 模块的魔力在 /apache 目录根目录下的 INSTALL 文件中进行了描述。为了编译 Apache 守护程序,首先需要“配置”Apache 构建环境。这是通过精彩而神秘的 autoconf 生成的脚本 configure 完成的。您只需将以下内容添加到 ./configure 行

--add-module=/usr/local/lib/jserv/mod_jserv.c.

这将生成一个 Makefile,该 Makefile 将 mod_jserv.c 文件复制到 ./src/modules/extra 目录,并将其编译到 Apache 中。请注意,仅仅将 mod_jserv.c 文件复制到 extra 目录 不会 完成任何操作。为了使这个新模块共享(假设 apache 中配置了 DSO),请将以下行添加到 configure 命令行

--enable-shared=jserv
此选项将生成一个 .so 共享对象,该对象在必要时动态链接到 Apache。

显然需要小心。我从 apache SRPM 中的 apache.spec 文件中抄袭了大部分细节;我建议您获取此文件并安装它。在 spec 文件中进行更改并重建 RPM (rpm -ba apache.spec),然后安装新的 RPM。这仅适用于基于 RPM 的系统(Red Hat、SuSE、Caldera 等)。

配置和测试 Apache/JServ

清单 1。

需要在您的 httpd.conf 文件中添加一些标签。它位于我稍微修改过的 Red Hat 5.1 机器的 /etc/httpd/conf 中。清单 1 是我文件中的摘录。清单 1 中的所有标签都在 JServ 文档中涵盖,并且 JAVA_COMPILER 实际上仅适用于安装了 JIT 的系统。请注意,所有相关的类路径都已包含:不需要 Swing 和其他此类可视化类。我还将 LD_LIBRARY_PATH 设置为 Java 共享对象。如果您的 Java 代码依赖于未位于 /etc/ld.so.conf 文件中列出的目录中的共享库,则必须将这些目录添加到 LD_LIBRARY_PATH。此外,JServ 错误日志 (ServletErrorLog) 是一个重要的配置项。所有与 JServ 相关的错误和异常都将转储到此文件中。还要密切关注常规的 httpd ErrorLog 文件。在测试期间,我在 JServ 错误日志文件上运行命令 tail -f

您的当前 Apache 守护程序现在必须重新启动。您如何执行此操作将因您的发行版而异,但对于 Red Hat 系统,只需使用 SysV 脚本即可完成魔法

/etc/rc.d/init.d/httpd stop
/etc/rc.d/init.d/httpd start

一般来说,应该可以简单地杀死 httpd 守护程序的所有实例,然后重新运行新的可执行文件

killall httpd
/usr/sbin/httpd
现在让我们获取一个小 Servlet 来测试启用 JServ 的 Apache 的当前功能。清单 2 中的 Java Servlet 说明了 Servlet 模型的优势和简单性。此代码保留一个计数器 (hits),该计数器在 Servlet 初始化时重置为零。每次 Apache 调用 Servlet 时,都会显示并递增计数器。请注意,没有努力保持 Servlet 及其数据的持久性。

清单 2。

每个 Servlet 都必须提供一个 service 方法,只要 Apache 需要服务 URL http://roger.dodger.org/servlet/ServletOne,就会调用该方法。传递的两个参数 HttpServletRequestHttpSerletResponse 封装了 HTTP 连接。可以使用这两个对象操作有关 HTTP 特定数据(如 cookie)的信息。方法 init 仅在类首次由 Apache 加载(或重新加载)时调用一次。此初始化方法可用于设置长期存在且可能成本高昂的资源。从这个简单的例子可以看出,类一旦被 Apache 加载,就会保持持久性,直到它被重新加载。 hits 变量初始化一次(在 init 方法中),然后在每次调用 service 时递增。如果 JServ 自首次加载以来已修改类,则 JServ 将重新加载该类。有时,如果属性文件发生更改,则需要重新加载类;在这种情况下,touch 类文件。这会非常透明地发生。

PostgreSQL 和 JDBC

Servlet 最常在与数据库连接相同的上下文中被提及,这是理所当然的。Servlet 及其持久性使其成为理想的数据库/Web 集成技术。我最初的尝试很快让我得出结论,CGI 脚本对于除大规模数据库处理之外的任何事情都太慢了——在数据库处理中,数据库连接的初始设置与实际数据库操作相匹配(甚至超过)。由于我熟悉 Red Hat 5.1 安装附带的 PostgreSQL,因此我决定使用该 RDBMS 进行实验。我还具有开发 Java 数据库客户端软件的一些经验。JDK(Java 开发工具包)中的 Java 数据库连接 (JDBC) 类易于使用,并且在数据库之间具有一定的可移植性。JDBC 类由数据库无关代码(java.sql.* 类,JDK 的一部分)和数据库特定代码组成。PostgreSQL 的 JDBC 类是较新发行版的一部分,对于此应用程序是必需的。

清单 3。

所有这些都可以通过一个小的 JDBC 示例清楚地演示出来。为了使这个示例有用,必须有一个可用的 PostgreSQL 安装。清单 3 是一个相当密集的示例,说明了许多 JDBC 问题。首先,设置 PostgreSQL JDBC 驱动程序 (loginJdbc) 和登录数据库 (loginUrlloginUserloginPasswd)。变量 loginUrl 通常很难弄对(尤其是在学习时)。最后一部分,在本例中为“dbname”,是要连接的数据库的名称。后两个常量 (loginUserloginPasswd) 需要更改以反映您的数据库环境。设置数据库的权限涉及执行命令 createuser(作为用户 postgres),然后 授予 用户所需的权限。建立连接后,SQL SELECT 语句将被执行,结果集将由变量 rs 捕获。然后使用此结果集的元数据 (rsmd) 来确定返回的列数并显示列名。然后遍历结果集,将每一行打印为字符串。此示例非常简单,仅适用于 SQL SELECT 语句,因为存在结果集。请为异常做好准备;例如,如果没有返回任何列,则会抛出异常。

留言簿应用程序

我的第一个留言簿应用程序是一个 Perl CGI 脚本,它将所有访客条目保存在格式化的文本文件中,该文件根据参数的指示进行显示或添加——简单而有效。不幸的是,我想对这些信息做更多的事情,包括链接回 Apache 日志文件,甚至可能发出 cookie 来监控使用情况。这个脚本最初很小巧精简,但开始因必要的文件处理而窒息。这也令人有些不安,因为我安装了一个功能非常强大的 RDBMS 并准备好使用它。我研究了替代方案 (mod_perl) 并决定使用 Servlet。第一步是设计表。最重要的表在行上保存每个留言簿条目。

CREATE TABLE guest_book (
        entryid         INT NOT NULL,
        response        CHAR(8),
        name            VARCHAR(32),
        addr            VARCHAR(48),
        email           VARCHAR(24),
        time            DATETIME,
        comment         TEXT,
        PRIMARY KEY (entryid)
)

留言簿表中的每一行都由 entryid 列唯一标识,该列具有隐式索引。此外,这些键的处理由键表处理,这在小型应用程序中很方便,但在大型应用程序中几乎是必不可少的。

CREATE TABLE key_table (
        id                      INT NOT NULL,
        val             INT DEFAULT 0,
        PRIMARY KEY (id)
)
键表能够跟踪 int 类型范围内的任意数量的键,在我的例子中是 [-231,231-1]。为了获得新键,必须检索当前键,然后递增。这必须是原子的,即操作必须在事务中发生。这在 getKey 方法中进行了说明。理想情况下,存储过程将处理所有这些细节(在理想的世界中,键表概念不应应用程序开发人员可见),但 PostgreSQL 的这种细节级别涉及 C 共享库和数据库 API——我不想参与其中。

需要解决的另一个问题是如何以一致的方式为 Servlet 提供属性文件。属性文件提供了一种方便的方式来放置原本会被硬编码到应用程序中的数据。这是通过在命令行上为 JServ 提供一个名为 base.dir 的属性来完成的,该属性指向一个世界可写目录(但 chmod +t,以便用户可能不会踩踏其他用户的文件)。这在 httpd.conf 文件中的 ServletBinaryArgument 标签中指定。在此目录中,您可以存储 Servlet 可以加载的属性文件。我相信可以用更智能的方式完成此操作。

主要处理入口点是 service 方法。从这里,要么显示条目列表 (listEntries),要么显示表单 (showForm),要么创建新条目 (addEntry)。由于空间限制,未显示留言簿应用程序的代码,但包含在 ftp.linuxjournal.com/pub/lj/listings/issue67/3243.tgz 的存档文件中。

清单 4。

清单 4 是用于设置 Servlet 各个部分的属性文件。我将许多 HTML 标头和页脚字符串以及 JDBC 配置参数移动到了此文件中。

结论

Servlet/JDBC/PostgreSQL 被证明是一种强大而快速的技术。我遇到的大部分问题都是配置问题,需要我仔细阅读相关文档。不幸的是,关于 Servlet 的技术文档很少,我建议进一步实验。我对这个应用程序采取的下一步是安装 Sybase ASE for Linux,这是一个我熟悉的 RDBMS。然后,我编写了许多存储过程,允许 Servlet 将其大部分数据操作委托给数据库,这才是它真正应该存在的地方。如果您要用 Sybase 替换 PostgreSQL,则需要从 Sybase 网站获取 jConnect JDBC 类。当然,这可以用 PostgreSQL 完成,但学习 RDBMS C API 的细节与练习无关。

本文中引用的所有清单都可以通过匿名下载在文件 ftp.linuxjournal.com/pub/lj/listings/issue67/3243.tgz 中获得。

Bruce McDonald (bruce@triphop.dyn.ml.org)

加载 Disqus 评论