将 DBMS 存储过程封装为会话 EJB 组件中的方法
集成 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 ( |
这个数据库还包含一个特别重要的存储过程。 过程 CLIENTREPORT 提供了注册的站点用户的综合概要信息,包括他们的投资以及 在公告板上讨论的问题。客户名称和电子邮件地址也包含在报告中,因此,销售部职员 就可以联系客户,向客户提供或许他们会感兴趣的其它产品和服务的建议。这就是我们想封装在 会话 EJB 组件中的过程。
由于该过程可以使用多种语言编写(包括 Java 编程语言),这里就不显示它的完整内容了。总之, 源代码实际上已不重要,因为您不能假想总是可获得它们。但是,为了让您对存储过程内容有一些了解,下面列出了其中包含的 3 条 SELECT 语句:
CLIENTREPORT 存储过程中的 SQL 语句
select name, e-mail from client where id = ? |
问号表示该语句依赖于运行时调用者的输入;在这里,调用者必须提供代表感兴趣客户标识的有效数据值。 正如您看了这些语句后或许可以猜想到的,存储过程需要一个输入参数(指客户标识),返回两个输出参数(指客户名称和电子邮件地址),并且返回两个结果集(一个包含有关客户投资组合的数据,另一个包含有关客户在公告板张贴内容的数据)。
![]() ![]() |
![]()
|
现在让我们进行 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 |
注:如果您使用 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 组件中用于封装存储过程(任何类型)的通用编码模式。
![]() ![]() |
![]()
|
客户机应用程序的第 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 组件并处理其返回的对象,而不必事先知道该对象的内部结构。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130508/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/374079/viewspace-130508/