集成 Web 应用程序服务器和数据库管理(DBMS)

来源:互联网 发布:qq三国四象js用什么 编辑:程序博客网 时间:2024/05/18 03:28

 Cynthia M. Saracco

  软件工程师, IBM

  2003 年 3 月

  集成 Web 应用程序服务器和数据库管理(DBMS)技术是许多新型商业应用程序的常见要求。我们

将在本文探讨那种集成工作的一个方面:如何在会话 Enterprise JavaBeans(EJB)组件中设计和开

发能够 封装或调用现有的 DBMS 存储过程的方法。想要充分理解本文,您应该熟悉 EJB 技术的基本

知识、结构化查询语言(SQL)以及 Java 数据库连接(JDBC)。

  如果您正在开发基于 Web 的应用程序,并且这些应用程序需要访问或修改 DBMS 中的数据,那么或

许您会倾向于基于 EJB 的设计。您会发现,让 EJB 组件利用 DBMS 存储过程可以减轻编码和维护工

作,并且可能提高数据访问性能。

  许多公司多年来一直使用存储过程,主要是由于它们减少了分布式计算环境中的网络流量,并使性能

得以提高。通常,这些过程包含了涉及多个数据库操作的重要业务逻辑。远程应用程序调用这些过程

,而过程中包含的 SQL 语句就会在 DBMS 服务器上执行。当然,过程完成后所有结果都会返回给应

用程序。

  包含在这些 旧存储过程中的业务逻辑对 Web 应用程序通常都是有用的。与其在 EJB 组件中复制该

逻辑,为什么不在会话 bean 中将包含逻辑的过程 封装为方法而加以使用呢?这样就可以在 DBMS 服

务器和 EJB 组件中避免冗余代码 — 在考虑开发、调试和维护等开销时,冗余代码会使生产效率下降

。附带的好处是,性能可能会得到改善。调用存储过程可以减少 EJB 组件本来不得不发出的 SQL 语

句数量,从而减少与远程 DBMS 的通信开销。

  入门

  为什么要从会话 bean 调用存储过程,下面就让我们集中讨论如何开始。首先,需要一个合适的开发

环境,它应该包括一个带有内置 EJB 支持的 Java 开发工具、一个 Web 应用程序服务器和一个关系

DBMS。我的参考配置包括 VisualAge for Java 企业版 3.0.2、WebSphere Application Server 高级版

3.0.2.1 和 DB2 V7.1;这些都安装在一台 Windows NT 系统上。有关如何配置该环境以支持本文所述

工作的详细信息,请参阅( 参考资料中的)“Leveraging DBMS Stored Procedures through Enterprise

JavaBeans”或查阅产品手册。

  有了适当的软件环境,就可以着手工作了。我们将探讨的编码模式最适合无状态会话 EJB 组件,尽

管它也可以应用于有状态会话 bean。但是,由于无状态会话 bean 消耗的系统资源比有状态会话 bean

要少,并且包含的代码较少,因此通常将其做为首选。

  首先要考虑的设计问题是如何映射存储过程和 EJB 组件之间的数据。存储过程可能需要多个输入、

输出和输入/输出参数,并返回一个或多个结果集(代表多行数据)。除非想对不同类型的过程使用

不同的编码模式,您需要编写 EJB 组件,以便它们能够处理所有这些可能性。

  处理输入(或者输入/输出)参数相当简单的:将存储过程所需的每个参数映射成 EJB 组件的输入

参数。而处理存储过程的输出比较棘手。或许会有多个输出参数和多个结果集要传回给调用者;这些

必须作为单个、可序列化的对象返回,以符合 EJB 规范。可以编写自己的类,使它能将数据打包到单

个对象同时包含所有必需的元数据。(该元数据描述了对象的内部结构,以使客户机知道如何处理它

。)不过那需要大量工作。

  如果正使用 VisualAge for Java 和 WebSphere,则有个更好的选择:使用它们的数据访问 Bean(

Data Access Bean)(DAB)库。该库包含一些类,这些类提供了在基本的 JDBC 之上的函数层。您

会发现 com.ibm.db.CallableStatement 类特别方便,因为它使您能够创建可序列化对象,该对象包含

由存储过程返回的所有输出,包括多个结果集(如果存在)和相关的元数据。还有一个额外的好处,

该库设计成支持任何支持 JDBC 的数据源,因此它使 bean“与 DBMS 无关”。有了 DAB 库,就可以在

会话 EJB 组件中使用单一的编码模式封装任何存储过程。甚至可以在 EJB 客户机中使用单一通用编

码模式处理由封装器方法返回的任何结果。

  回顾开发任务

  让我们探讨一下使用通用编码模式集成 EJB 组件和 DBMS 存储过程所采取的步骤:

  确定要将哪个存储过程封装成 EJB 方法。如果该过程不存在,按照您的 DBMS 标准过程创建并调试

  确定要使用哪个无状态会话 EJB 组件。如果该 EJB 组件不存在,按照您的 Java 开发环境的标准过

程创建并调试。

  继承 EJB 组件的远程接口,以包含用来封装存储过程的新方法。

  继承 EJB 组件的实现,以包含用来封装存储过程的新方法的逻辑。连接到数据库、调用存储过程、

处理任何结果及处理异常,这些是后面要解决的问题。

  通过构建一个客户机应用程序或 servlet 调用 EJB 组件的封装器方法来测试您的工作。

  前两步是基本编程任务,可能您已经熟悉。根据所用产品的不同,特定的步骤可能有所不同,但是大

多数产品都有提供帮助的工具。例如,如果您使用 VisualAge for Java 和 DB2,可以使用“存储过程构

建器”(Stored Procedure Builder)完成第一步,而使用 EJB 开发功能完成第二步。前两步不是本文

讨论的重点。但是会对其余三步做详细研究。本文会在应用示例的环境中逐个讨论。

  研究应用案例

  假定我们需要构建一个支持某公司销售部的应用程序,该公司负责维护一家面向金融的网站。这个网

站可以让人们注册成为客户、跟踪其投资组合并在电子公告板上张贴评论。我们还假定支持该网站的

数据存储在 DB2 表中。下面的代码样本显示了如何创建这些表。

  在 DB2 中创建样本表的 SQL 语句

 
  create table client (

  id int not null primary key,

  name varchar(30),

  email varchar(30),

  phone varchar(12),

  regdate date,

  mktg char,

  constraint check1 check (mktg in ('y', 'Y', 'n', 'N'))

  )

  create table portfolio (

  id int not null,

  clientID int not null references client,

  ticker varchar(10) not null,

  cost decimal (9,2),

  qty int,

  date date,

  primary key (id, clientID, ticker)

  )

  create table boards (

  msgno varchar(15) not null primary key,

  subject varchar(40),

  date date,

  clientID int not null references client

  )
  这个数据库还包含一个特别重要的存储过程。过程 CLIENTREPORT 提供了注册的站点用户的综合概

要信息,包括他们的投资以及在公告板上讨论的问题。客户名称和电子邮件地址也包含在报告中,因

此,销售部职员就可以联系客户,向客户提供或许他们会感兴趣的其它产品和服务的建议。这就是我

们想封装在会话 EJB 组件中的过程。

  由于该过程可以使用多种语言编写(包括 Java 编程语言),这里就不显示它的完整内容了。总之,

源代码实际上已不重要,因为您不能假想总是可获得它们。但是,为了让您对存储过程内容有一些了

解,下面列出了其中包含的 3 条 SELECT 语句:

  CLIENTREPORT 存储过程中的 SQL 语句

 
  select name, e-mail from client where id = ?

  select id, ticker, cost, qty, date from portfolio where clientid = ?

  select msgno, subject, date from boards where clientid = ?

  问号表示该语句依赖于运行时调用者的输入;在这里,调用者必须提供代表感兴趣客户标识的有效数

据值。正如您看了这些语句后或许可以猜想到的,存储过程需要一个输入参数(指客户标识),返回

两个输出参数(指客户名称和电子邮件地址),并且返回两个结果集(一个包含有关客户投资组合的

数据,另一个包含有关客户在公告板张贴内容的数据)。

  修改 EJB 组件的远程接口

  现在让我们进行 EJB 组件代码的工作。

  由于我们希望使封装器方法对 EJB 组件客户机可用,因此需要继承 bean 的远程接口。我们将使用一

个称为 Analysis 的无状态会话 bean,并包含一个存储过程封装器的 lookupClient 方法。该方法需要单

个整数作为输入来代表我们希望得到其报告的客户标识;它返回一个 DAB CallableStatement 对象(

可以在 com.ibm.db.* 包中找到)。任何由该过程返回的异常将会转换为 RemoteExceptions(对符合

EJB 1.0 的会话 bean 适用)。

  下面的编码示例显示了 EJB 组件远程接口的修改部分。

  EJB 组件远程接口

 
  // Enterprise JavaBean Remote Interface for Analysis session bean

  public interface Analysis extends javax.ejb.EJBObject {

  // remote interface for our lookupClient method

  com.ibm.db.CallableStatement lookupClient(java.lang.Integer clientId)

  throws java.rmi.RemoteException;

  . . .

  }
  注:如果您使用 VisualAge EJB 组件向导,则无需为此进行手工编码。在对 bean 实现类中的方法编

码之后,可以调用一个菜单项将这个方法提升(promote)为 bean 的远程接口,所需代码会自动添加

  对存储过程封装器方法进行编码

  现在我们准备专门讨论 bean 实现类本身;在这个类中包含了用来调用存储过程以及将所有输出作为

com.ibm.db.CallableStatement 对象返回的代码。这个 代码样本包含 lookupClient(...) 方法的完整实现

,该方法将调用 CLIENTREPORT 存储过程。下面,我们会研究每个代码块的逻辑(参考代码中的注

释),这样可以更好地了解如何对您的存储过程实现类似的方法。

  连接到数据库

  让我们仔细研究一下代码样本的各个部分。

  在调用一个存储过程之前,需要与 DBMS 建立连接。有两种方法:使用 JDBC 1.0 风格的连接或

JDBC 2.0 风格的 DataSource。后者因为提供了连接合用(connection pooling),能更有效地使用系

统资源,所以在 WebSphere 环境下通常它是首选。出于这个原因,我们的编码模式使用了

DataSource。

  除了要确定希望建立的连接类型之外,还应当考虑在 bean 的什么地方放置连接逻辑。您有多个选择

  直接放在封装器方法中

  放在私有助手方法中

  将其放在 ejbCreate() 方法中(而将相应的断开连接逻辑放在 ejbRemove() 方法中)

  权衡这些方法超出了本文的范围。为了简化起见,我们的样本把所有连接/断开连接逻辑直接放在封

装器方法中。

  第 1 个代码块说明当使用 VisualAge for Java 3.0.2 和 WebSphere 3.0.2.1 时,如何使用 DataSource

进行连接。我们创建了一个散列表,在其中填充适合 WebSphere 环境的值,并建立一个 InitialContext

。接下来的几行代码利用这个最初的上下文和 Java Naming and Directory Interface(JNDI)服务获得

对期望的 DataSource 的引用,这个 DataSource 是我们以前在 WebSphere 中使用“管理控制台”(

Administrative Console)创建的。本例中,DataSource 名为 LocalDB2Sample。下面,要使用这个

DataSource 获取连接,将相应的数据库用户标识和密码传递给它。从池中获取连接之后,可以将该信

息传送给 DAB DatabaseConnection 对象以建立其所需的连接规范。最后,将 autoCommitMode 设置

为 false,因为 EJB 组件负责处理事务管理服务。

  为了测试目的,最好能够在 VisualAge for Java WebSphere Test Environment 中运行使用

DataSource 的 EJB 组件。有关如何在本产品的 3.0.2 发行版中实现的指示信息,请参阅 David

Zimmerman 所著的“Creating DataSources in the VisualAge for Java WebSphere Test Environment”(

参考资料)。

  调用存储过程

  建立连接后,现在可以专门讨论调用存储过程了。如封装器方法编码示例的第 2 个代码块所显示的,

我们一开始先创建 DAB StatementMetaData 对象,它会为存储过程保留一个规范。接下来,定义要

执行的 SQL 语句。这里,将调用 CLIENTREPORT 过程,它需要一个输入参数(用于客户标识)和两

个输出参数(用于客户名称和电子邮件地址)。下一步,将参数添加到规范中。对于该过程的每个参

数,指定一个参数名称、其数据类型及其参数模式。

  第 3 个代码块创建即将执行的 DAB CallableStatement 对象。CallableStatements 代表可以用来执行

存储过程的 SQL。创建对象之后,将其元数据设置为第 2 个代码块所指定的数据。再将

DatabaseConnection(在第 1 个代码块中创建)与 CallableStatement 关联。

  接下来的任务很简单:我们要执行 CallableStatement 对象,这将使 DBMS 运行存储过程。但是,这

样做之前,我们必须将过程的输入参数设置成由 EJB 客户机应用程序传递给方法的值。第 4 个代码块

显示了一个逻辑。

  检索存储过程的输出并返回给调用者

  在封装器方法编码示例的第 5 个代码块中,我们检索两个由存储过程返回的输出参数。正如您能回忆

起的,这些参数表示网站客户的名称和电子邮件地址。但是,无需显式地检索由存储过程返回的结果

集。(这些结果集包含了有关客户投资组合和公告板张贴内容的数据。)您可能想知道,这样做的原

因所在。

  某些 DBMS 要求在获取任何输出参数值之前,要从存储过程返回的结果集中检索所有满足要求的值

。因为有这个要求,CallableStatement bean 直到通过 getParameter() 方法发出特定请求时才从数据

库获取输出参数,因为用户控制了何时检索结果集的值。缺省情况下,存储过程执行后,会自动检索

结果集的所有行并存储在高速缓存中。但是,必须始终显式地检索输出参数并存储在高速缓存中。

  检索了输出参数,然后我们将 DAB CallableStatement 返回给 EJB 组件的调用者。该对象现在包含

所有由过程返回的输出(包括结果集)和相应的帮助调用者正确解析对象的元数据。当研究调用会话

bean 封装器方法的样本客户机应用程序时,将看到如何去做。

  如果熟悉 JDBC,或许您想知道为什么我们不在这个代码块中显式地发出一条 commit 语句。实际上

,如果使用了 JDBC 1.0 风格的连接,需要显式地发出一条 commit 语句(否则的话,当我们在“finally”

代码段中关闭数据库连接的时候,我们的工作将会回滚)。但是,使用 DataSource 和接受

WebSphere 用于 EJB 组件(TX_REQUIRED)的缺省事务属性使 WebSphere 能为我们的工作自动提

供事务管理服务。因此,不再需要显式的 commit 语句。

  处理异常和关闭打开的资源

  当然了,会话 bean 执行时可能会发生错误。所以需要提供异常处理。第 6 个代码块包括一个简单的

、适用于符合 EJB 1.0 的 bean 异常处理程序。它只捕获遇到的任何异常,包括相应的出错信息,并

将异常作为新的 RemoteException 抛给调用者。

  另外,这个代码块包含一个“finally”块,以确保关闭由方法打开的资源。到此,我们释放了所有与

CallableStatement 对象相关联的资源。接下来,要除去任何对工作中使用的连接的 DAB 引用。最后

,确保关闭 WebSphere 连接。

  构建客户机应用程序

  构建好 EJB 封装器方法后,该专门讨论客户机应用程序了。跟 EJB 组件一样,首先介绍客户机应用

程序的完整代码样本。然后详细研究各块代码。

  这里显示的客户机应用程序 ClientAnalysis 使用 RMI/IIOP 与 EJB 组件进行通信。其工作很简单:创

建会话 bean、调用它的 lookupClient(...) 方法、处理从方法返回的 DAB CallableStatement 对象及除去

bean。编写这个应用程序作为通用客户机处理 CallableStatement;也就是说,我们无需预先知道任何

CallableStatement 内部结构。相反,我们严格依赖其中包含的元数据来解析对象,并且处理其相应的

组件(例如由过程返回的输出参数和结果集)。该方法阐述了一种可以在任何应用程序中使用以便处

理 CallableStatement 的通用编码模式。正因为如此,它补充了无状态会话 EJB 组件中用于封装存储

过程(任何类型)的通用编码模式。

  创建 EJB 组件并调用其封装器方法

  客户机应用程序的第 1 个代码块以 main(...) 方法开始。它指定了感兴趣的客户标识并调用私有助手方

法获取将使用的会话 EJB 组件。创建 bean 后,会调用其 lookupClient(...) 方法。该方法用来封装

CLIENTREPORT 存储过程及返回 DAB CallableStatement。

  私有助手方法 — createEJB() — 值得仔细研究。我们选择将 EJB 组件的创建工作分割为单独的方法

,因为根据所使用的 Web 应用程序服务器,代码可能需要稍微进行更改。特别是,由于与上下文关联

的某些特性是不同的,JNDI InitialContext 的获取方法会有所不同。

  createEJB() 方法创建了散列表,并将与软件环境相适应的值填充进该表。接下来,创建一个新的

InitialContext 对象,该对象用于获取对使用 JNDI 服务的 EJB 组件宿主的远程引用。这个远程引用比

它从 JNDI 上下文返回之前明显地变小了 — 这是在 IIOP 上使用 RMI 时的一种编码需求。一旦获取了

EJB 组件宿主,就可以创建无状态会话 bean 并将其返回给客户机应用程序的 main 方法。

  处理返回的对象

  客户机应用程序的第 2 个代码块处理由 EJB 组件返回的 DAB CallableStatement 对象。首先定位与

CallableStatement 相关联的根元数据对象。由于 CallableStatement 可以包括多个结果集,所以多个

StatementMetaData 对象可以链接在一起并包含在 CallableStatement 中。链的根始终都包含描述

SQL 语句和相关参数的元数据,因此,这就是我们的着手点。这使我们能够得到 CallableStatement

中包含的参数数量。返回的数字将包含过程所有的 IN、INOUT 和 OUT 参数。通过使用循环,可以处

理所有参数并打印每个参数的相关信息,包括参数名称、对应的 Java 类和模式(表示 IN、INOUT 或

OUT 模式的数字)。

  接下来,检查结果集并进行处理。首先,确定 CallableStatement 对象中包含的结果集数量。使用循

环,可以得到每个表示为 DAB SelectResult 对象的结果集。然后使用另一个私有助手方法

processRS(...) 来处理结果集。 processRS(...) 方法确定传递给 SelectResult 并包含在其中的行和列的

数目。假定存在一些行,该方法会使用嵌套循环的方式打印所有行中所有列的信息。该信息包含列名

及其值。

  到这里,客户机应用程序的工作接近完成。第 3 个代码块除去会话 bean,打印一行表明已完成的信

息,然后终止。当然,遇到的任何异常都由第 3 个代码块后的代码来处理。这里,只打印堆栈跟踪。

  结束语

  希望您现在理解了会话 EJB 组件是如何利用封装在 旧DBMS 存储过程中的业务逻辑。这样做所带来

的可能好处包括减小 EJB 服务器和 DBMS 之间的网络流量、提高生产率并降低总的软件维护费用。

如果遵循本文概括的编码模式,则无论与过程相关的参数和结果集如何,您都可以在无状态会话 bean

中将任何类型的存储过程封装为方法。此外,还可以使用通用编码模式调用任何这样的 EJB 组件并处

理其返回的对象,而不必事先知道该对
 

原创粉丝点击