2008年(3500)
分类:
2008-05-04 20:28:42
Noel J. Bergman (noel@jspdevguide.com)
CTO,Development Technologies,Inc./DevTech
2001 年 9 月
本文讨论使用 JSP 和 JDBC 技术把静态的、动态的及数据库内容集成在 Web 站点中。为了简洁明了的说明问题,文中的 JSP 页面使用短的 scriptlet 让 JSP 开发者接触到底层的 JDBC 概念,而不是把其隐藏在定制标记中。作者介绍一种集成 JavaBeans 组件和 JDBC 的主要设计方法,该方法同一直以来 JavaServer Pages 技术采用的、把 bean 用于 HTTP 的方式类似。他还提供实现该集成的代码。请在讨论论坛同作者及其它读者分享对本文的理解。
JavaServer Page(JSP)技术建立在 Java Servlet 技术的基础之上,是核心的生成动态内容的服务器端 Java 体系结构。关系数据库是动态内容的来源之一。Web 站点使用关系数据库存储各类信息:目录项、图像、文本、关于注册成员的资料等等,从而管理从在线社区到电子商务交易的一切事务。本文讨论通过 Java 数据库连接(JDBC)把 JSP 技术应用于关系数据库。正是 JDBC 使 Java 程序可以使用关系数据库。
要深入理解本文,您应该熟悉 JDBC 和 SQL。
JDBC 基础知识
JDBC 是 Java 代码和 SQL 数据库之间的一座桥梁。主要的 JDBC 对象表示同数据库的连接及利用这些连接执行的语句。用于关系数据库的两种基本语句是查询和更新。两者都需要的一个前提条件就是,您首先要利用
java.sql.DriverManager
类同数据库建立连接。建立连接要花很长时间(就计算机时间而言),因此在
Web 服务器这种事务繁忙的环境中,您希望尽可能重用连接。这样的重用叫做建立连接池。
如果您的 JDBC 技术有些荒疏了,那么清单 1 中的代码片断说明的是同一个测试数据库建立 connection、创建用于该 connection 的 statement(语句)对象、发出 SQL 查询、处理 results(结果)及释放 JDBC 资源的全过程:
清单 1. 简单的 JDBC 代码
|
在现实生活中,JDBC 代码不会如此简单;因为需要处理异常和警告情况。清单 2 说明的是同一个
JDBC 示例,但添加了对 JDBC 异常和警告的处理。在这个示例中,异常和警告只记入日志,并且,对于异常情况,我们将异常终止操作。不过 finally{}
子句将确保资源清除过程进行。
对真实的结果处理过程在此仅作一下提示;我们将在本文后面的部分接着更仔细的分析这个问题。如果我们正在执行的不是数据库查询而是更新,那我们可以把
while
循环替换成如下语句:
int count = statement.executeUpdate(sqlUpdate);
除 executeQuery()
和 executeUpdate() 之外,Statement 类也支持一般的 execute()
方法。虽然这允许编写一般的 SQL 代码,但处理结果的过程更复杂了。 |
executeUpdate()
方法返回 update
语句所作用的行的总数。
如果这些代码清单中的材料显得陌生,您也许希望花些时间重温一下在参考资料部分中找到的一些 JDBC 教程信息。
JDBC 用于 JSP 页面
那么我们怎样结合使用 JDBC 和 JSP 技术才能使我们的动态内容来自数据库呢?
作为一条普遍的法则,良好的 JSP 惯例建议您应当把表示同模式行为分离。这与面向对象编程中的“模式-视图-控制器(MVC)”范例类似。分离原因之一是,基于 JSP 技术的应用很可能由程序员编写“模式(Model)”和“控制器(Controller)”组件,而由页面设计人员编写“视图(View)”组件。就 JSP 应用体系结构而言,负责表示的“视图”角色由 JSP 页面处理。负责对请求做出响应的“控制器”的角色通常由 servlet 担当,但许多 JSP 用户开始认识到用 JSP 页面来担任“控制器”的角色的优点。“模式”这一角色负责为应用实体的行为构建模式,典型情况下由 JavaBean 组件来担当。
除确定在 MVC 范例中的何处同数据库交互之外,关于在 JSP 页面中集成 JDBC 技术您有多种选择。例如,您可以使用 scriptlet 插入 JDBC 或使用标记库插入,还可以把它隐藏在定制标记或其它的类里。接下来我们要看看一些方式的示例并讨论其用法。
JSP scriptlet 示例
JSP 程序员新手有可能做的第一件事是写一个 scriptlet 来访问 JDBC。也许写出来的这个 scriptlet 象清单 3 中的示例一样使用
JDBC 来实现该页面的“点击计数器”。(这个页面的实际版本位于
JavaServer Page Developers Guide Web 站点上)
|
该页面中有一个 scriptlet(高亮的第一部分)连接到数据库、创建 statement(语句)并试图对一个以页面的 URI 为主键的计数器记录进行更新。如果更新没有对任何行起作用,那么这个示例就假定没有这样的记录并添加一条。最后,这个 scriptlet 查询数据库中当前点击计数并把结果赋给一个局部变量。继续往下看,这个 JSP 页面的“表示”部分中高亮的 JSP 表达用于显示点击计数器的值。
注:您也许不希望真的以这种方式实现点击计数器。为每一次请求而更新数据库的开支是不必要的开销。但是,我们可以用点击计数器这个对数据库进行更新和查询的简单示例来说明在您的 JSP 页面中集成 JDBC 的各种途径。 |
尽管 JSP 页面能起作用,但它有许多问题。首先,不编程的页面设计者不会希望在页面中看到 scriptlet。坦白的讲,连程序员也不会希望在页面中有这类内容,因为它使页面的实际内容变得杂乱。第二,为简化示例,这个页面缺少异常处理,而这个异常处理应该是实际的 JDBC 代码中的一部分。第三,点击计数器的实现的确是嵌在 JSP 页面里的,因此对点击计数器的任何更改都需复制到使用点击计数器的每个 JSP 页面。
那么我们怎样修改这个 JSP 页面呢?一种常用的解决方案是使用标记库换掉 scriptlet。在下一个示例中,我们会看一下这种可选择的解决方案。
使用 DBTag 的标记库示例
通常善意的朋友和专家会告诉 JSP 程序员新手一些基本知识,其中之一就是不要使用 scriptlet。相反,他们会让 JSP 程序员新手使用定制标记。我们利用定制标记扩展
JSP 平台的功能:同代码库相连的定制 XML 样式标记实现预期功能。我们将会在下个示例中看到 DBTag 的工作情况。
Jakarta TagLibs Project 是 Jakarta Project(请参阅参考资料)的一个子工程,该工程是 Java Servlet 和 JavaServer Pages 技术的官方参考实现。
在 Jakarta TagLibs Project 的支持下开发的一个包是 DBTag 定制标记库(以前叫做 JDBC 标记库)。清单 4 里的 JSP 页面把 scriptlet 替换成定制标记,实现的点击计数器和清单 3 中的一模一样。
清单 4. JSP 页面使用 DBTag
|
我不知道您的感受如何,可我感觉到有一点失望。在我看来这似乎还不如 scriptlet 示例清晰,而且我知道也不会有哪个不编程的 HTML 页面设计者会对此感到满意。但是问题出在哪儿呢?毕竟,我们已听从了大多数人的建议:我们抛弃了 scriptlet,用定制标记取而代之。
开发定制标记库相对简单些,但也要费些思考,而且费时。我常推荐的做法是标记库的作者们先用
scriptlet 为标记行为建立原型,再把这些 scriptlet 变成标记。
一种可选择的解决方案是使用 Allaire 的 JRun Server Tag(JST),使您可以通过把每个标记作为一个 JSP
页面(以 |
关于标记库 Allaire 没有告诉您的是:标记设计是一种语言设计。迄今为止,所写的大多数标记库是由程序员为程序员写的;这些标记的语义适合于其它的程序员。不仅如此,还记得模式和表示的分离吗?DBTag
对这一点不太支持。sql:getColumn
标记同 jsp:getProperty
动作类似:它把标记的结果直接发给输出流。这给
DBTag 的利用与把输出转化成要求的格式之间的分离带来了困难。最后,请注意清单 3 和 清单 4 的逻辑差异。DBTag 的 execute
标记使用所有来自通过 JDBC 发送的 update 语句的更新计数;可以回收的只有查询结果。这意味我们无从知道 update
语句更新了多少行。因此我们不得不切换 update
和 insert
语句;我们总是试图插入新记录、迫使
DBTag 怱略任何错误,然后执行更新。
对 DBTag 标记库公正的评价是,对于程序员来说这是个不错的标记库。除更新计数的使用外,代码提供的与 JDBC 的映射相当不错。但其中也存在问题:标记提供的只不过是对 JDBC 包的直接翻译。除隐藏了一些异常处理之外,标记库实际上并没有提供对 scriptlet 任何抽象。这对从功能中分离表示当然于事无补。
因此,真正的问题不在于是使用 scriptlet 还是使用标记;这种问题不是分离功能和表示的问题的原因,而是结果。解决方案是要以适当的说明级别上为表示页面的作者提供更高级别的功能。认为标记优于 scriptlet 的原因在于:根据定义,scriptlet 是编程,而标记可以表示高级概念。
对表示页面隐藏 JDBC
将 JDBC 同 JSP 技术集成时,我们希望对表示作者尽可能多的隐藏该集成。在我们显现数据库概念的地方,我们希望在恰当的抽象级别上显现这些概念。这一方法引出了我们的下一个示例。
在清单 5 中的示例中,我们对表示页面隐藏了 JDBC 集成。(这个页面的实际版本位于 JavaServer Pages Developers Guide Web 站点)
清单 5. 隐藏了 JDBC 的 JSP 页面
|
被包含的 hitCounter.jsp
文件负责设置环境。内容可以是 scriptlet、标记或只是一个 taglib
伪指令;只要是为表示页面建立预期环境的都可以作为内容。如果您愿意,您可以把 getProperty
操作替换成定制标记;例如:
|
如前所述,这些点击计数器示例纯粹用作说明;每个页面都执行这样的数据库操作将会是不必要的开销。上面这个示例说明事实上您希望以何种方式显现点击计数器。通过把它隐藏在定制标记里,我们就把该实现彻底隐藏了起来。现在我们可以聚集点击计数运行时信息,并周期性的更新数据库(比如,在每次会话结束时)。甚至连存储方式(数据库或是别的)也对表示页面作者隐藏了。这正是我们在
DevTech 实现点击计数器的方式:我们利用 bean
类实现点击计数器模式行为。标记把该行为结合到了我们的页面里。
集成 JavaBean 组件
到目前为止,示例一直都相当简单,但大多数数据库操作都比这些简单的查询和更新要复杂。因此,既然我们已经讨论了把 JDBC 用于 JSP 页面的一些基本原则,就让我们用一个略微复杂些但肯定更加通用的一类应用来结束这篇文章。
这一部分的示例(下面清单 9)将指出一种方法以支持 Web 站点上访问者提供的内容。换句话说,我们希望允许访问者读取与某一 URI 关联的数据库内容并撰写额外的内容。这样的内容在现在的 Web 站点上相当常见。同样的基本部分可用于构造:
通过由不同技术背景的设计人员编写,本例中 JSP 组件稍微再精细些的版本就可以实现看上去截然不同的 Web 页面。这些页面看上去的唯一的相同之处就是用于访问者撰写内容。
我们的 annotation(注释)示例使用 HTML 表单。HTML 表单用于 JSP 时,使用属性映射到表单域的 bean 变得方便了。这使
setProperty
标记可以变戏法:
|
映射 bean
和 ResultSet 示例使用 com.devtech.sql 以使说明简洁。示例使用 Java Reflection 和 Introspection,依靠列名及属性名提供
JDBC 数据和 bean 属性间的映射。您可以把 DevTech 包替换成自己的代码。 |
集成 JavaBean 组件是 JSP 技术设计方面的一个过人之处。不幸的是,bean 和 JDBC 之间的集成根本不是无缝的,因此我们针对 DevTech 的 JDBC 工作开发了一个包,它不仅提供了 bean 和 JDBC 的集成,而且还有必要的异常处理,从而使程序员不必应付这些细节。
annotation(注释)示例使用 com.devtech.sql
包中的两种查询和更新方法。这里用到的查询方法传递一个
bean
类、一个 SQL 查询和一个 Object
数组填充查询中的占位符。在这种情况下,仅有的占位符是给页面的
URL 的。结果是一个数据库游标对象,它必须是一种迭代器。
|
这个查询方法的有趣之处在于指定类型的 bean 将会为您实例化,只要 bean 属性名同 ResultSet
中的列名相对应,属性值就会被自动设置。每次您用游标选择下一行时,bean
的属性就会根据 ResultSet
被自动设置。
用到的这种特殊更新方法有一个 bean
实例、一个 String
数组及一条 update
语句作为参数。String
数组值规定了用来填充更新中的占位符的预期 bean 属性。在这种情况下,page
、author
和 annotation
属性是根据 bean 选择的。
|
我们的 JSP 页面示例 annotations.jsp 如清单 9
中所示。高亮的部分指出两个可以用定制标记替换的 scriptlet,如清单 10 中所示。一些为页面设计者提供帮助的 JSP 组件、把动态内容放到页面上去的
getProperty
操作以及标准 HTML 组成了页面的剩余部分。使用 JSP 注释的原因是因为 JSP 注释是私有的,不会出现在输出流中。
|
定制标记等价程序清楚,但不提供信息:
清单 10. 定制标记等价程序
|
我们在这个示例中使用 scriptlet 只是要让您(一位程序员)知道正在发生的事情。如果用陈述性的标记替换 scriptlet,那么页面设计者会很清楚,但您却一头雾水。
逻辑并不复杂。annotation.getCursor()
调用能得到同服务器的连接、发出查询并在结果集建立一个数据库游标对象
annotations
。每次调用 annotations.next()
时,从结果集中取出一个新行,其中的值移到
bean 里,方法返回的正是这个 bean 的引用。所用的这个特殊的 next()
方法使用一个要植入的 bean
参数。虽然我们本可以使游标为每一行实例化一个新的 bean,但反复使用一个 bean 更加高效。
请注意真正的查询及更新都没有在表示页面中出现。被包括的页面设置表示页面环境,也包括 setProperty
和
update
操作。这些操作独立于表示页面;只有注释 bean 的属性所包含的 contract 是重要的。这遵守了将表示与模式行为相分离的策略。页面设计者完全有能力更改表示是如何呈现的,但无从得知数据库是如何集成的。如果更改在数据库更新或查询时生效,就应当由
JSP 程序员负责。
总结
本文对结合使用 JavaServer Page、JavaBean 和 JDBC 技术并通过关系数据库生成动态内容的进行了总结性介绍。我们从对于
JSP 程序员新手来说最显而易见的方法(scriptlet)开始。我们看到不加控制的使用 scriptlet 是如何使逻辑和表示纠缠在一起的,使两者都难以维护。我们还看到标记库并不一定会改善
MVC 分离,如果用编程术语来表示标记,则页面设计人员可能无法理解使用这样的标记的页面。最后,我们看了更为复杂的示例,这些示例说明了几种使数据库访问与内容表示分离的途径。
现在您应该对在对页面设计人员隐藏真实的数据库访问的同时如何把数据库内容集成到一个 Web 站点中有一些基本概念。还请注意,对您(一个程序员)而言,几乎不含信息的那些示例是最适合于页面设计人员的示例。当您为 JSP 解决方案制定计划时,千万要为页面设计人员设身处地地想一想。
关于作者 Noel Bergman 在面向对象编程方面的经历持续 20 多年,包括参与最早的 CORBA 和“公共对象服务任务小组(Common Object Services Task Forces)”。Colorado Software Summit 及其它的业界会议上他一直作为受欢迎的演讲者享有盛誉;他还担任导师和顾问,为客户提供量身定制的服务。 目前,Noel 正涉足于使用开放源代码免费软件为手持设备编程以及开发交互式以数据库为后台的 Web 站点。最后,Noel 还是 GNUJSP 的合著者,GNUJSP 是 JavaServer Pages 技术的开放源代码实现,而且他还首创了 JSP Developers Guide Web 站点。 |