ITPub博客

首页 > Linux操作系统 > Linux操作系统 > [转载]我们针对各种全异的数据源开发实体 EJB 的经验

[转载]我们针对各种全异的数据源开发实体 EJB 的经验

原创 Linux操作系统 作者:dinner1007 时间:2019-02-25 19:00:06 0 删除 编辑

我们针对各种全异的数据源开发实体 EJB 的经验


我们的作者们与读者分享了他们的经验,并比较在使用和不使用 DB2 Information Integrator 进行联邦数据库访问这两种情况下,实现跨越不同后端的数据源的实体 Enterprise JavaBeans 的相对代价。

简介

如果您和许多服务器端 Java™ 开发人员的情况差不多,就可能会发现自己为处理分散在各种数据源中的数据伤透脑筋。在先前的文章中,我们描述了 Java 2 企业版(Java 2 Enterprise Edition,J2EE)程序员是如何使用联邦数据库管理(DBMS)技术来减轻其负担的。其中至少有两篇文章: 构建跨越联邦数据的实体 EJBAccessing Federated Databases with Application Server Components向使用 IBM® WebSphere® Studio 的 Application Developer 配置的实体 Enterprise JavaBeans(EJB)开发人员提供了循序渐进的指示信息。

我们想接着这些文章继续进行讨论,研究一下联邦数据库技术会带来多少益处。我们实现了一些需要访问不同数据源中数据的实体 EJB。为了使文章富有趣味性,我们将关键数据存储在两个关系 DBMS(DB2® Universal Database™ 和 Oracle)和一个电子表格系统(Microsoft® Excel)中。显然,如果仅使用关系数据源,则我们的工作可能会比较简单。可是,谁又能说生活总是简单的呢?

我们使用 WebSphere Studio V5 的 Application Developer 配置作为开发和测试平台。

  • 在一种方案中,我们依靠 IBM DB2 Information Integrator 来模拟数据源的单点映像。我们称之为 联邦实现
  • 在另一种方案中,我们构建了实体 EJB 以直接访问所需的每个数据源。我们称之为 本机数据访问实现

(和许多人一样,在两种情况下,我们还构建了作为实体 EJB 前端的外观(facade)会话 EJB。但我们将稍后讨论这个问题。)下面讲述我们开发的故事:为了使每种方案成功,我们做了些什么,以及最终学到了什么。

我们希望这种经验能使您深入地了解当使用和不使用联邦 DBMS 来构建这些类型的 EJB 时,所面临的富有挑战性的开发工作。对于那些对此类琐碎细节不感兴趣的读者,我们在下一节中作出了结论。而对于确实喜欢钻研设计和编码问题的读者,则可以通读后续的几节以得出自己的结论 — 并看它们是否与我们的结论一致。




回页首


我们学到了什么

我们并不认为开发工作会很简单,但也不会因此气馁。我们已经很认真地处理了所有设计问题,但在实现时还是会遇到一两个意外。

但是,我们确实发现,当使用联邦 DBMS 时,整个任务确实比使用本机数据访问要简单得多。使用联邦 DBMS,设计更为清晰,实现阶段更为迅速,并且,我们最终只需要手工编写极少的代码来进行测试和维护(实际上,比原来的一半还少)。我们意识到这些优点,主要是因为联邦 DBMS 使我们能够构建具有容器管理的持久性(CMP)的单个实体 EJB 来执行所需的任务。

没有联邦服务器的情况下,我们需要构建三个单独的实体 EJB:一个用于 Oracle 数据、一个用于 DB2 UDB 数据、另一个用于 Excel 数据。此外,因为我们需要依靠来自于第三方的免费 JDBC/ODBC 桥来访问 Excel 数据,所以不能使用 CMP 实体 bean 来为这种数据建模。而必须通过构建具有 bean 管理的持久性(BMP)的实体 EJB,来编码自己的持久性机制。当然,这会带来更多的工作。

这些体系结构性问题具有连锁效应。与许多 EJB 开发人员一样,我们意识到除了拥有通过 bean 的主键值找到该 bean 的缺省搜索机制之外,我们的 bean 还需要支持更灵活的搜索机制。当然,这意味着我们必须实现定制 finder 方法,就象许多 EJB 开发人员所做的那样。对于 CMP 实体 EJB 的联邦实现,这并不太困难;我们只需用 EJB 查询语言(EJB Query Language,EJB QL)编写一个 finder 方法即可。因为存在与 EJB QL 相关联的限制, 我们不能按自己的意愿在查询中表达所有内容(稍后将进行更详细的讨论),但我们可以针对测试案例尽力克服这些限制。

对于实体 EJB 的本机数据访问实现,情况又有些麻烦了。我们只能将 EJB QL 用于 CMP EJB(Oracle 和 DB2 UDB 数据)。因为 BMP EJB 不支持 EJB QL,所以我们必须在定制的 finder 方法中手工编写 SQL 查询的代码,使用 JDBC 来执行这些查询,以检索必需的数据。因为我们已经熟悉了 SQL,所以这并不太难。但是,它确实意味着:不使用联邦 DBMS,要实现我们的解决方案,就要求我们精通两种查询语言 — EJB QL 和 SQL。

最后,我们的实体 EJB 实现还会影响外观会话 EJB 的工作。有了 联邦实现,会话 EJB 的工作就相对简单了:

  1. 查找符合条件的那一个实体 EJB,
  2. 调用其定制 finder 方法,然后
  3. 处理其结果。

使用 本机数据访问实现时,会话 EJB 必须:

  1. 逐个地查找三个符合条件的实体 EJB,
  2. 调用每个 EJB 的定制 finder 方法,
  3. 将每个方法返回的结果集成起来,
  4. 最终按照所期望的方式处理组合的结果。

此外,这还需要更多的思考、精力和时间来进行编码和测试。

我们的联邦实现的一个缺点就是必须使 CMP 实体 EJB 是只读的(通过修改 EJB 部署描述符中的 Access Intent 设置),因为我们将该 EJB 映射到了一个不可更新的视图。对于我们稍后将要讨论的应用程序方案,这是可以接受的。

如果这一切使您对我们的工作感到好奇,那就读下去。我们将在讨论过程中向您讲述设计问题,并讨论相关代码样本。如果到目前为止,您感觉读起来已经有些困难,也别着急。接下来的两节将带着您快速地浏览一遍联邦 DBMS 和 EJB 技术。




回页首


理解概念

这一节是为那些不熟悉联邦数据库管理系统或 Enterprise JavaBeans(EJB)的用户准备的。

联邦 DBMS

我们已经多次讨论了“联邦 DBMS”这个术语,如果您还没有读过 DB2 开发者园地中有关此主题的先前的一些文章,有可能会好奇:这样一个系统到底是什么样子的?

用最简单的话来说,联邦 DBMS 就是一种虚拟数据库服务器。它提供了用来访问多个数据源的单一应用程序编程接口(API)。这些数据源可能运行在不同的硬件和操作系统平台上,可能是由不同的供应商开发的,也可能使用不同的 API(包括不同的 SQL“方言”)。程序员使用联邦服务器在更高的抽象级别上工作,而用其它方法,可能达不到这样的级别,因为该服务器提供了实际全异数据的单点映像。使用表(或其它数据对象,譬如文件)的 昵称向程序员提供了位置透明性,使他们无需确切地知道想要的数据位于哪里。功能补偿可以掩盖不同供应商的产品之间的差异,并模拟给定数据源上原本不支持的能力。多表连接(join)和联合(union)促进了来自多个数据源的数据的集成。

联邦 DBMS 技术于上世纪 90 年代以各种名称面市;当时的报纸通常将此类产品称为下一代网关、数据访问中间件和多数据库服务器。IBM DB2 DataJoiner® 是 IBM 的这种技术的第一个商业发行版。DB2 通用数据库 V8 Linux 版、UNIX® 版和 Windows 版平台现在为精心选定的一些数据源提供了内置联邦 DBMS 支持,这些数据源包括 DB2 系列、Web 服务和 WebSphere MQ。DB2 Information Integrator 为大量的其它关系和非关系数据源提供了联邦支持,稍后我们将进行讨论。DB2 Information Integrator 还有一个特点是具有可扩展体系结构,该体系结构使用户可以定制联邦 DBMS 来支持对其选定的数据源的访问。

图 1中显示了样本联邦 DBMS 服务器体系结构。在这种环境中,Java 程序员可以编写基于 JDBC 的应用程序来连接到服务器。这种服务器又与在不同平台上的、由不同供应商支持的数据源进行相互操作。结果,程序员不必了解每种数据源的底层 API,就可以用 JDBC 应用程序来访问这些数据源中的任何一个。此外,可以创建涉及这些数据源的视图,以简化只读应用程序的数据集成问题。


图 1. 使用 DB2 Information Integrator 的样本联邦 DBMS 环境
使用 DB2 Information Integrator 的样本联邦 DBMS 环境

数据源支持和产品功能因商业产品而异。DB2 Information Integrator 支持:

  • IBM DB2 系列的所有成员
  • IBM Informix®
  • Microsoft SQL Server
  • Oracle
  • Sybase
  • 支持 ODBC 的数据源
  • XML
  • Web 服务
  • WebSphere MQ
  • Excel 电子表格
  • 平面文件
  • 生命科学数据源

此外,DB2 Information Integrator 还能够通过 IBM Lotus® Extended Search 访问 Web 搜索引擎、内容资源库、电子邮件数据库以及其它基于内容的数据源。

因为 DB2 Information Integrator 包含功能完整的关系 DBMS,所以它可以存储和管理其自身的本地数据对象,如表、视图和索引。其优化器旨在考虑其环境的全异和物理分布式的本质,以便它可以为每次查询都选择有效率的数据访问策略。DB2 Information Integrator 的这个发行版支持在单个事务中从多个数据源读取数据;并支持在每个事务中对一个数据源进行写操作。

EJB

EJB 是实现最小行为集的服务器端软件组件,它以简化应用程序开发和有助于提高可移植性的方式封装业务逻辑。生产应用程序经常需要对一些功能(如事务、安全性和持久性)提供支持,它们都是由 EJB 规范管理的。可以通过使用 RMI/IIOP 协议直接从客户机端 Java 应用程序访问 EJB;也可以从 Web 客户机间接地访问 EJB,在间接访问的情况下,Web 客户机通过 HTTP 与 Web 服务器通信,然后再调用 servlet、JSP 或 Web 服务,它们随后访问 EJB。

在部署时,EJB 位于提供各种服务的 容器中,我们将不讨论其中大多数容器。但是,我们已经间接地提到了一个由容器管理的重要功能:对持久性的支持。CMP 实体 EJB 依靠 EJB 容器来实现和管理对目标数据源的访问。相反,BMP 实体 EJB 需要 EJB 开发人员来实现和管理对持久性数据的访问。

正如先前提到的那样,EJB 2.0 规范描述了几种类型的 EJB,我们将在这里讨论其中一部分。 会话EJB 通常封装了应用程序可能希望调用的功能或业务服务,而 实体EJB 则代表持久性数据。EJB 开发人员可以创建利用 JDBC 对受支持的 DBMS 进行读/写访问的会话 bean。实际上,会编写许多会话 bean 来执行一些数据库操作或事务型工作。但是,EJB 容器将任何与会话 bean 相关联的数据都看作是瞬态的;容器不会自动地对这些数据提供持久性支持,在应用程序完成其会话 bean 的工作之后,会话 bean 所声明的任何变量的值都将不复存在。相反,实体 EJB 被认为是处理持久性数据的。开发人员可以自行管理这种持久性(通过 bean 管理的持久性),或者可以将这种职责委派给容器(通过 容器管理的持久性)。

不同类型的 EJB 向 EJB 开发人员提出了不同的编码需求,并且这意味着可用于客户机的最少服务会因 EJB 类型的不同而有所不同。例如,在部署时,每个 CMP 实体 bean 都有几个代码模块,包括:

  • 远程接口,它定义了与该 bean 相关联的业务方法。“ getter”和“ setter”通常用来读取/设置 bean 的单个属性。
  • 远程 home 接口,它定义了用于创建、查找和除去 bean 实例的客户机方法。
  • 本地接口(EJB 2.0)向同一容器中的其它 EJB 提供的服务与远程接口所提供的相同。这避免了分布式对象协议的开销,从而改善了性能。
  • 本地 home 接口(local home interface)(EJB 2.0)向同一容器中的其它 EJB 提供的服务与远程 home 接口所提供的相同,执行的效果也相同。同样,这避免了分布式对象协议的开销,从而改善了性能。
  • bean 类,它包含 EJB 开发人员编码的业务逻辑方法以及容器使用的 EJB 生命周期方法。EJB 客户机不直接访问这个类的对象,而是使用容器生成的类(该类实现 home 或远程接口)来间接使用这个类的服务。
  • 主键类,它标识了唯一地标识该 bean 的每个实例的属性(或一组属性),并提供了用于创建和操作键的方法。

会话 bean 也拥有上面描述的所有接口以及 bean 类。但是,它们没有主键类。

在开发 EJB 之后,程序员必须设置 部署描述符,它们控制诸如事务支持、隔离级别等特征。最后,必须将 bean 打包并部署在 EJB 服务器中。部署过程会导致生成其它类,包括那些与先前描述的 远程、远程 home、本地本地 home接口相关联的类。合适的 Java 开发环境(譬如 WebSphere Studio)对于开发、部署和测试 EJB 非常有帮助。




回页首


我们的样本方案

要研究使用联邦 DBMS 来支持实体 EJB 开发的设计和开发利弊,我们用适合于我们项目的数据填充三个不同的数据源 — Oracle、DB2 和 Excel。我们推断这会支持这样的商业方案:其中多个企业(在我们的案例中是部件分销商)将合并成一个公司,或者希望在一个合资销售企业中进行合作。在这种情况下,每个企业都有可能将其数据存储在不同的数据源中,这迫使我们的应用程序集成来自于每个数据源的数据。我们的目标很简单:希望 Web 用户能够搜索位于任何或所有这些数据源中的各种部件。每个数据源中都包含着类似的数据,这些数据是关于可用于来自分销商订单的部件的,包括唯一的部件键、名称、类型和零售价格等。

我们从支持一种非常简单的搜索功能开始:使用户能够找到他们感兴趣的部件。我们编写了一个定制的 EJB finder 方法,使用户能够(在三个数据源中)根据部件的类型和名称搜索 PART 数据。用 SQL 表达时,样本搜索看起来如下:

Select p_name, p_mfgr, p_type, p_partkey from part
where p_type like '%BURNISHED%' 
and p_name like '%KNOB%'
order by p_partkey 
fetch first 20 rows only;

当然,实际上我们的 EJB finder 方法看起来并不是这样。我们没有将查询搜索谓词的值硬编码到方法中,并且没有使用 SQL(除了用于 Excel 的 BMP 实体 EJB 的 SQL)。但是 SQL 毫无疑问比 EJB QL 更流行,我们想让您大致明白我们正在设法实现什么。就查询而言,这确实相当简单。




回页首


两种方法的设计步骤所具有的问题

本节描述了联邦方法和本机数据访问方法的设计问题。

联邦设计

在定义了应用程序目标之后,我们需要解决一些关键的设计问题。对于联邦实现,这包括联邦数据库模式的定义。换句话说,我们必须解决如何向 J2EE 程序员表示 PART 数据的问题。通常,数据库管理员会承担这个任务或至少协助完成该任务。

定义联邦数据库对象
我们在联邦数据库中创建了对象,它们促进了对 DB2 UDB、Oracle 和 Excel 数据的透明访问。尤其是,我们为每个数据源管理的数据定义了昵称(nickname),并基于这些别名创建了 UNION ALL 视图。这样,可以将所有 PART 数据看作是位于联邦数据库的一个逻辑表中。这使我们能够构建涉及所有全异数据源的单个 CMP 实体 EJB — 这是一种简单、清晰的解决方案。

严格来说,UNION ALL 视图(它保留了重复的行)对于我们的工作并非必需;我们本可以轻松地使用 UNION 视图(它消去了重复的行)。但是,特定的 UNION ALL 视图定义包括了附加的“服务器属性”列,稍后我们将发现它对于向 WebSphere 标识复合的主键很有用。这一点多少有些难以理解,因此我们将进一步研究它。

实体 EJB 开发人员必须标识 bean 的一个或多个属性,这些属性保证每个对象的唯一性。换句话说,如果创建了一个 Part 实体 EJB,至少必须要有一个属性充当其主键。这使得软件组件能够使用 EJB 的 findByPrimaryKey() 方法,通过传入给定的主键值(譬如下例中的“1”)来检索单个 Part:

  
p_partkey            p_name                 . . . 
      1              Foo
      2              Foobar
      6              Fusilli

当将实体 EJB 映射到单个物理表时,这样做当然很好。但是,当将实体 EJB 映射到涉及多个数据源的逻辑视图时,事情就变得复杂了。在关系 DBMS 中,主键是与物理表而不是逻辑视图相关联的。类似地,唯一索引是针对表进行定义的,而不是视图。这意味着 EJB 开发人员在使用视图时必须小心,以确保 EJB 主键的值确实是唯一的。

有些糊涂吗?让我们继续讨论示例。假定您希望将 CMP 实体 EJB 映射到一个视图,而该视图联合了来自于 Oracle 和 DB2 UDB 的数据。(暂时忘掉 Excel — 它不是关系表,因此本来就没有主键的概念。)让我们进一步假定 Oracle PART 表和 DB2 UDB PART 表分别在其自身的 p_partkey 列上定义了主键。一切都很正常,是吗?只要定义一个视图将两个表联合到一起就行了,是吗?错了。

关系型联合操作符消去了重复的 。您有可能会碰到一种情况,即 Oracle 和 DB2 UDB 只有 p_partkey 值相同而 p_name(或其它列)值不同的行,如下所示。

DB2 PART table:  
   p_partkey              p_name                 . . . 
        
        1                 Foo
        2                 Foobar
        6                 Fusilli
        ...               ...
Oracle PART table: 
   p_partkey              p_name                 . . . 
        1                 Farfalle
        8                 Fettuccine
        2                 Foobar
        ...               ...    
 

因此,这些行并非真的相同,因此联合操作不会过滤掉它们。相反,联合 Oracle PART 表和 DB2 UDB PART 表(如上所示)的视图生成:

UNION view:  
p_partkey              p_name                 . . . 
      1                 Foo
      2                 Foobar
      6                 Fusilli
      1                 Farfalle
      8                 Fettuccine 
      ...               ...

请注意,两行都包含值为“1”的 p_partkey,这使得该列不适合作为充当 EJB 主键值基础的唯一属性。

但请不要失望。可以有一些选择。

最显而易见的选择是扩展 EJB 的主键定义以包含更多从底层的表中派生的属性。尽管这样做很诱人,但它有可能会引起将所有的列(或者,至少是视图中的所有列)包括到 EJB 的主键定义中。这种方式所受的限制太多,有时完全不切实际。

另一个选择是细心地制定视图定义以消除问题。这个解决方案要好得多。但我们如何实现这一点呢?嗯,我们选择将新的 server_attribute 列添加到视图定义,然后用有关数据源的信息填充它,我们感兴趣的行就是从这些数据源产生的。这样,EJB 主键定义就涉及了 p_partkey 列和新的 server_attribute 列,如下所示。在这个示例中,db2_part、ora_part 和 odbc_part 是我们先前为 DB2 UDB、Oracle 和 Excel 所管理的 PART 数据所定义的昵称。

CREATE VIEW fed_part AS
  SELECT db2_part.*, 'db2' AS p_server
  FROM db2_part
UNION ALL
  SELECT ora_part.*, 'ora' AS p_server
  FROM ora_part
UNION ALL
  SELECT odbc_part.*, 'xls' AS p_server
  FROM odbc_part

这种方法使我们能够保护 EJB 主键定义(及其相应的强制 home 接口方法)的完整性,同时又能从各种数据源检索我们想要的全部数据。注:Excel 电子表格所维护的部件键具有唯一值。

设计定制 finder 方法
正如我们先前(在 我们的样本方案中)所提到的,我们希望在实体 EJB 中实现定制 finder 方法,以支持对具有某些特征的部件的搜索。要做到这一点,我们计划用 EJB QL 编写一个查询。事实证明这个设计是可行的,尽管我们会失望地发现 EJB QL 有某些局限,稍后我们将讨论这个问题。

但是,既然您知道了我们是如何设计联邦实现的,首先让我们研究如何设计本机数据访问实现。

本机数据访问设计

当直接使用每个数据源时,我们知道必需创建多个实体 EJB 来代表存储在 Oracle、DB2 UDB 和 Excel 中的 PART 数据。我们原本希望能够为每个数据源创建一个 CMP 实体 EJB,但在对 Excel 这样做时遇到了麻烦。

使用 BMP 和 CMP 实体 EJB
尽管市场上有各种 JDBC/ODBC 桥可用,但我们的桥不支持 DataSource 对象,WebSphere 将这些对象用来池化连接。因此,我们不能依靠 CMP 实体 EJB 设计来处理 Excel PART 数据。我们必须转而实现 BMP 实体 EJB。

与任何其它东西一样,BMP 实体 EJB 有其优点和缺点。就优点而言,它们非常灵活,而且实际上可以针对任何数据源实现它们。就缺点而言,这种灵活性和大范围数据访问的代价很高昂:您(EJB 程序员)必须编写所有的数据访问代码。特别地,您必须确保实现和测试所有强制的 EJB 方法,而使用 CMP 实体 EJB 时则不必为此操心。(EJB 容器在部署 CMP 实体 EJB 时生成这些方法。)这要求更高级别的编程技能,并意味着更长、更复杂的测试周期。它还增加了作业的维护成本。

将 EJB QL 和 SQL 用于定制的 finder 方法
我们实现 BMP 实体 EJB 的需求还意味着不能轻松地重用简单的 EJB QL 查询,因为 EJB QL 仅可用于 CMP 实体 EJB。因此,尽管我们为基于 Oracle 和基于 DB2 的 PART 实体 EJB 编写了 EJB QL 查询,但我们必须在 Excel PART 实体 EJB 中编写自己的基于 SQL 的方法。Excel 定制 finder 方法看起来很象微型 JDBC 应用程序,而用于 Oracle 和 DB2 UDB 的定制 finder 方法却只有几行 EJB QL 代码。

为每个数据源设计查询
当直接使用不同数据源时,我们预计一些查询可能需要作些修改,以适应不同的本机 API。幸运的是,在我们的测试案例中,WebSphere 的 EJB QL 支持负责针对 Oracle 和 DB2 UDB 进行必需的转换。此外,我们的查询非常简单,因此足以能够通过 JDBC/ODBC 桥发出标准 SQL 语句来检索需要的 Excel 数据。

但是,我们很容易找到一些不那么走运的情况。这些情况可能包含某些聚集操作以及对数据子集的检索(访存前 n 行)。这个问题值得进一步研究。

当您正在构建需要直接使用多个数据源的应用程序或 J2EE 组件时,必须由您确定如何分解您希望用来获得结果的查询,以使效率最大化同时又保持正确的查询语义。有时,说起来容易做到难。

通常,您希望尽可能多地将数据过滤工作推给数据源。例如,我们的查询只对某些类型的(那些名称和类型与一些用户指定的条件匹配的)部件感兴趣。如果我们从数据源检索所有部件,然后在 EJB 中过滤掉不符合要求的部件,将是十分低效的。但是,如果查询包含聚集、GROUP BY 子句或 FETCH FIRST n ROWS 子句,则您必须在简单地将原始查询传递到每个数据源之前仔细地考虑查询语义。如果不这样做,那么,在尝试合并最终结果时,您最终会得到错误的信息。

如果这听起来有些令人糊涂,那么考虑一个简单的示例。假定有两个银行 BankA 和 BankB 合并了,并且希望确定它们(合并后)客户的平均帐户结余。如果 John Doe 在 BankA 有两个帐户,结余分别是 200 美元和 400 美元,他在 BankB 还有一个结余为 900 美元的帐户,则该客户的平均结余是 500 美元。(200 美元 + 400 美元 + 900 美元 = 1500 美元。1500 美元/3 = 500 美元。)但是,如果包含这个客户的记录的每个数据源都计算了 AVG 结余,而又将结果合并起来并再次计算总的 AVG,结果有可能会大不相同。BankA 的数据源会返回 300 美元的平均结余,BankB 的数据源将返回 900 美元的平均值,而包含合并结果的数据源所计算出的“最终”平均值为 600 美元(300 美元 + 900 美元 = 1200 美元。1200 美元/2 = 600 美元)。这个结果显然是错误的,但每个数据库都正确地处理了查询并且没有报错。

因此,确定如何正确地分解复杂查询,以便不影响性能同时又不威胁工作的完整性,这可能是一个费时并且容易出错的过程。还有,我们的测试案例非常简单,因此实际上我们不一定会遇到此类问题。可是,值得指出的是,您未必会如此走运。使用 DB2 Information Integrator 时,其全局查询优化器将自动为您处理这项工作。




回页首


我们是如何构建实现的

在本节中,我们描述了联邦方法和本机数据访问方法中都会出现的实现问题。

我们的联邦实现

我们的联邦实现由一个 CMP 实体 EJB 和一个无状态会话 EJB 组成。Fed_part 实体 EJB 被映射到三个数据源联邦数据的 FED_PART 视图。Fed_part 有一个定制 finder 方法 findRowsForQ1(),在会话 bean SessionQ1Fed 的 getQ1Results() 方法中调用了该方法。这个会话 bean 充当了 CMP bean 的前端(或称为外观)。请参阅 图 2


图 2. 使用 DB2 Information Integrator 进行实体 EJB 的软件设计
使用 DB2 Information Integrator 进行实体 EJB 的软件设计

下面几节描述了如何在设置了开发环境之后构建这些 EJB,我们在 项目描述中讨论过开发环境。

CMP 实体 EJB
我们使用 WebSphere Studio 标准的自顶向下建模方法来开发映射到 DB2 联合视图的 CMP 实体 EJB。我们为查询所需要的每个列创建了属性,它们都是从 FED_PART 视图派生的。

接下来,我们按照标准的 WebSphere Studio 过程,使用 EJB QL 为这个 CMP bean 创建了一个定制 finder 方法,用来检索想要得到的查询结果。值得指出的是,EJB QL 限制了我们可以返回的输出类型。我们选择返回一组 bean 接口,因为我们预料许多部件可以与调用者的搜索条件匹配。由于 EJB QL 限制,我们不能表示查询的 fetch first 20 rows only 部分。但是,可以将查询的 order by p_partkey 部分放在后面,如下所示:

SELECT DISTINCT object(o) FROM Parts o
WHERE o.p_name LIKE ?1
  AND o.p_type LIKE ?2
ORDER BY o.p_partkey

当然,我们对谓词的 String 值使用了标记。在调用 finder 方法时它们作为参数传递。

如果您对我们为什么偏不使用 SQL 编写这个查询感到好奇,答案非常简单:EJB QL 是 CMP 实体 EJB 所支持的查询语言。如果希望通过 JDBC 直接用 SQL 表达查询,则需要构建 BMP 实体 EJB。这会涉及更多的工作,我们希望利用 CMP 实体 EJB 提供的优点,包括开发生产率和更高的可移植性。

会话 EJB
我们的会话 bean 使用 JNDI 服务来查找 CMP bean 的本地 home 接口,然后调用其定制 finder 方法。会话 bean 对返回的 CMP bean 集合进行迭代,从前 20 个 bean 中检索想要的数据(即列 p_partkey、p_name、p_typep_mfgr ),并将它存储在数组中。最后,会话 bean 将这个数组返回给调用者。

以下是会话 EJB 中的 getQ1Results() 方法,它们执行上面的步骤。

public Q1Tuple[] getQ1Results() {
       Q1Tuple[] result = new Q1Tuple[20];
               try {
                       javax.naming.InitialContext ctx = new InitialContext();
               /* get access to 'Parts' EJB local home interface
               *  "ejb/q1/PartsLocalHome" is a local EJB reference in the
	   * Deployment Descriptor.  You must prefix it with "java:comp/env/"               
               */
       PartsLocalHome home =   
       (PartsLocalHome)ctx.lookup("java:comp/env/ejb/PartsLocalHome");
               /* receive a collection of all rows (i.e. a list of beans) that
	     * satisfy our criteria, except for "fetch first 20 rows"
               */
                 AbstractCollection coll = 
                       (AbstractCollection)  home.findRowsForQ1("%KNOB%","%BURNISHED%");
                 Iterator beanIter = coll.iterator();
                 /* now we get the desired data from the first 20 entries only 
                 */
                 for (int i = 0; i < 20; i++) {
                               PartsLocal tempBean = (PartsLocal)beanIter.next();
                               Q1Tuple tempTuple = new Q1Tuple();
                               tempTuple.setP_partkey(tempBean.getP_partkey().intValue());
                               tempTuple.setP_name(tempBean.getP_name());
                               tempTuple.setP_type(tempBean.getP_type());
                               tempTuple.setP_mfgr(tempBean.getP_mfgr());
                               result[i] = tempTuple;
                       }
               } catch (Exception e) { e.printStackTrace(); }
               return result; 

新 Java 类
所请求的结果由两种不同的数据类型组成:用于 p_partkey 属性的 Integer 和用于所有其它属性的 String。自然,Java 中没有现成的数据类型可以用来保存这个结果 — 我们必须创建一个新数据类型。

要做到这一点,在 EJB 模块中使用 WebSphere Studio 的标准过程创建一个新的可序列化的 Java 类。我们的 Q1Tuple 类有四个字段用于保存四个列值,并有用来操作这些字段的 get()set() 方法。

我们将生成的所有行都存放在 Q1Tuple 对象中,并将所有的 20 行都作为 Q1Tuple 的数组返回。

事务作用域
会话 bean 在单个事务中调用适当的 CMP bean 方法。我们使用 EJB 部署过程来控制事务作用域,即编辑 CMP bean 的部署描述符来将所有适合于事务的方法设置为“Required”事务。

代码计数
我们只写了不到 200 行代码就实现了联邦解决方案。WebSphere 在部署时生成了附加的 EJB 代码,导致运行时代码总共达到了约 3100 行。

我们的本机数据访问实现

开发我们这项工作的本机版本,所花代价要高得多(正如所预料的)。我们要创建更多的实体 EJB,并且会话 EJB 的复杂性也增加了。 图 3说明了我们的体系结构。


图 3. 实体 EJB 使用本机数据访问的软件设计
实体 EJB 使用本机数据访问的软件设计

正如您可能注意到的,我们在这种方法中使用了三个实体 EJB 和一个会话 EJB。对于 Oracle 和 DB2 UDB,我们构建了 CMP 实体 bean;但是,我们必须为 Excel 编写 BMP 实体 bean。会话 EJB 从每个实体 bean 中调用方法,并充当调用应用程序的外观。

有必要提一下,尽管开发 BMP 实体 bean 的需求大大增加了我们的工作量,但它并不是迫使我们处理更多工作的唯一因素。实际上,即使可以仅使用 CMP 实体 EJB,我们也还是要创建更多的此类 bean。而这又会增加外观会话 bean 的复杂性,外观会话 bean 需要执行多个 JNDI 查找、调用多个定制 finder 方法、集成每个 finder 方法返回的结果、对集成的结果排序以及消除可能的重复项。

CMP bean
我们按照标准的 WebSphere Studio 开发过程来创建用于 Oracle 和 DB2 UDB 数据的 CMP 实体 bean。这些 bean 中的每一个都有用于我们需要的列的 CMP 字段。

我们将所有实体 bean 以及外观会话 bean 都放置到同一 EAR 文件中,以便后者可以访问实体 bean 的本地 home 接口。我们还为两个 CMP 实体 bean 创建了定制 finder 方法。我们同样是通过使用部署描述符编辑器中的向导,用 EJB QL 创建这些方法。除了表名之外,这个查询和联邦版本中的查询相同。当然,对用于联邦查询的 EJB QL 适用的限制在这里也适用。

BMP 实体 EJB
因为我们的 JDBC-ODBC 驱动程序不支持 JDBC 2.0 数据源,所以我们无法创建用于访问 Excel 电子表格的 CMP bean。相反,我们必须依靠 bean 管理的持久性(BMP)。BMP bean 要求开发人员实现所有 bean 方法,以及测试和维护这些代码。相反,使用 CMP 实体 bean,EJB 容器会在部署时生成必需的代码。

尽管 BMP 实体 bean 的数据访问代码相对易于实现(毕竟,我们只是编写了一些访问 Excel 的 JDBC 语句),但它所需要的工作量还是比实现 CMP 实体 bean 所需的工作量多得多。尤其是,我们需要为本机数据访问实现所编写的大约半数的代码是为了满足使用 BMP 实体 bean 的需求。而且,BMP 开发比 CMP 实体 bean 开发费时得多。对于缺乏经验的 BMP 程序员而言,设计、开发和测试工作往往要耗费数天或数周时间,这取决于访问目标数据源所需的数据访问代码的复杂性。即使是在我们这个简单的示例中,构建 Excel BMP 实体 bean 也需要几个小时的工作,以及额外的时间用于测试。相反,在 WebSphere Studio 中开发和测试简单的 CMP 实体 EJB 通常只要几分钟。

概念上,Excel bean 执行与 CMP bean 相同的功能。特别地,其本地 home 接口中也有一个方法 findRowsForQ1() ,用于访存满足查询的行。使用 BMP 最有利的方面是我们不必再受 EJB QL 的限制。相反,我们可以直接用标准 SQL 实现查询。

正如先前提到的,每个实体 bean 都需要主键类来提供唯一地标识自己的方法。对于 CMP bean,WebSphere Studio 自动生成主键类。但是,对于 BMP bean,必须由开发人员创建这个类,因为她/他是唯一知道哪个属性标识该 bean 的人。因为在我们的示例中 p_partkey 足以充当主键,所以我们基于这个属性开发了主键类。

会话 EJB
正如我们所预料的,用于本机数据访问实现的外观会话 bean 的实现要复杂得多。会话 EJB 查找我们的实体 EJB(两个 CMP bean 和一个 BMP bean)的本地 home 接口,然后调用与每个 EJB 相关联的定制 finder 方法。然后它必须对结果进行合并和排序。

至少有两种方法可以做到这一点:

  • 在本地数据库中使用临时表来对中间结果进行排序并消除重复的项
  • 使用 java.util.TreeSet 类来自动地对中间结果进行排序并消除重复的项

我们认为大多数 J2EE 程序员更倾向于实现第二种选择,因此我们也是这样做的。我们还预料到这种方法带来的开销和资源利用会比较少,尽管并没有显式地对此进行测试。

在调用了每个实体 EJB 定制 finder 方法之后,我们将返回的结果添加到 TreeSet 对象中,该对象透明地处理对结果的排序并消除重复的项。使用 TreeSet 对象意味着我们必须编写自己的实现 compareTo() 方法的比较器类。这个方法计算出集合中的一个对象比另一个对象小、大还是相等。 Q1TupleComparator 类为存储在 TreeSet 中的 Q1Tuple 对象实现这个方法。请参阅下面的 Q1TupleComparator.java 的代码:

import java.util.Comparator;
/**
 * Implements the Comparator interface. The Q1TupleComparator is used
 * to compare Q1Tuple objects.
 */
public class Q1TupleComparator implements Comparator {
	public Q1TupleComparator() {
		super();
	}
	public int compare(Object o1, Object o2) {
		//cast objects back to Q1Tuple
		Q1Tuple x = (Q1Tuple)o1;
		Q1Tuple y = (Q1Tuple)o2;
		//assign p_partkey values
		Integer ix = new Integer(x.getP_partkey());
		Integer iy = new Integer(y.getP_partkey());
		
		/* the result to be returned will be
		 *  < 0 , if ix < iy
		 *  = 0 , if ix = iy
		 *  > 0 , if ix > iy,
		 * where ix and iy contain p_partkey values
		 */
		return ix.compareTo(iy);
	}
}

完成这些工作之后,就可以着手实现外观会话 bean 了。正如您从下面的代码样本中看到的,这个 bean 从每个数据源中检索查询结果、将这些结果合并在一个集合(集合进行排序并消除重复的项)中,并将该集合返回给调用者。为了完成这个任务,会话 EJB 的 getQ1Results 方法依靠三个助手方法来检索结果: getDB2Results()、getOraResults()getXLSResults() 。我们没有在这里包括这些助手方法的源代码,因为它们多少有些冗长。

public Q1Tuple[] getQ1Results() {		
		//holds the final result
		Q1Tuple[] result = new Q1Tuple[20];
		/* create a comparator capable of comparing the string arrays in
 		 * the result set */
		Q1TupelComparator qtc = new Q1TupelComparator();
		//is used for sorting and duplicate elimination
		TreeSet resultSet = new TreeSet(qtc);
		try {			
			//get the DB2 results and append them to the result set
			resultSet.addAll(this.getDB2Results());
			//get the Oracle results and append them to the result set
			resultSet.addAll(this.getOraResults());
           //get the Excel results and append them to the result set
			resultSet.addAll(this.getXLSResults());
						
			//copy results from result set to result array
			Iterator resultSetIter = resultSet.iterator();
			for (int i = 0; i < 20; i++) {
				result[i] = (Q1Tuple)resultSetIter.next();
			}
		} catch (Exception e) { e.printStackTrace(); }
		return result;
}

新 Java 类
与联邦实现一样,我们需要创建可序列化的 Java 类来代表实体 EJB 返回的结果。这个 Q1Tuple 类与我们用于联邦实现的类相同,后者我们先前已经描述过了。

事务作用域
在建立涉及所有三个数据源的单个事务作用域时,我们遇到了麻烦。我们猜想至少有一部分麻烦是与 Excel 和 JDBC/ODBC 桥相关联的限制造成的。因为两阶段提交逻辑对于我们的应用程序并不重要,所以我们没有详细讨论这个问题。相反,我们在会话 EJB 的同一事务作用域中执行了 DB2 UDB 任务,在不同的事务作用域中执行了 Oracle 任务,而在事务上下文之外(具有“不受支持的(not supported)”事务属性)执行了 Excel 任务。

代码计数
我们编写了 545 行代码来实现本机数据访问方法,其中近半数的代码是为了满足编写 BMP 实体 EJB 的需求而编写的。WebSphere 在部署时生成了附加的 EJB 代码,导致运行时代码总共达到了约 5600 行。




回页首


理解 EJB QL 的限制

我们已经提到了在我们的工作中遇到的几个 EJB QL 限制。如果您是一位有经验的 SQL 程序员,又有一点 EJB QL 知识,那么可能会发现这种语言很容易学。它看上去确实很象 SQL。但是,您可能会感到惊讶,您想当然地认为可用的许多语言元素在 EJB QL 2.0 中都不受支持。这些包括对某些字符串比较操作、DATE/TIME 表达式、聚集、ORDER BY、GROUP BY、HAVING、EXIST、FETCH FIRST n ROWS 以及其它元素的支持。

在将来的 EJB 规范发行版中可能会取消其中一些限制,并且 WebSphere 提供了对 EJB QL 的扩展,以解决现有的许多问题。例如,我们能够在工作中使用 WebSphere 的 ORDER BY 子句支持。但是,FETCH FIRST n ROWS 不受支持,因此我们必须在外观会话 EJB 中编写适当的逻辑来实现这个功能。

此外,EJB 2.0 规范将 finder 方法可以返回的输出类型限制为以下三种类型之一:

  • 到 CMP bean 的接口
  • 一组 CMP bean 接口
  • 对象属性

因为我们的查询只涉及 PART 数据,因此很容易实现返回一组 CMP bean 接口的 finder 方法。但是,如果我们必须表达一些 SQL 查询,这些查询涉及多个 CMP bean 的属性,并且(有可能)动态生成数据,这就会产生一个问题。此类查询不会简单地返回几组现有 CMP bean 接口;实际上它们将返回新的对象类型。因此,我们需要重新评估实体 EJB 数据模型或考虑将会话 EJB 用于此任务。

但是,值得指出的是,WebSphere Studio 的 Enterprise Developer 配置(我们没有将它用于 Java 开发工作)为 WebSphere 开发系列中的 EJB 查询提供了最广泛的支持。例如,该产品的特点是具有支持动态执行 EJB 查询的 executeQuery() API,并具有 EJB 2.0 规范中原本并不支持各种表达式的语法(包括某些聚集、ORDER BY、GROUP BY、HAVING 和 EXISTS),并且具有查询输出方面更大的灵活性。这个 API 还支持一个可以用来限制返回结果集大小的参数,提供了类似于 SQL 的 FETCH FIRST n ROWS 子句的能力。这些扩展可能非常有用;但是,使用它们要权衡高级功能和可移植性之间的利弊。




回页首


结束语

构建需要集成来自于多个数据源数据的 Web 组件并不是一项简单的工作,但我们发现联邦 DBMS 技术有助于减轻许多与完成该任务相关联的负担。

我们构建了需要集成来自于多个系统的数据的实体 EJB。一个实现使用了联邦 DBMS,而另一个实现直接访问每个数据源。我们的经验显示:当我们使用联邦 DBMS 透明地访问涉及各种远程数据源的数据时,设计、开发和维护方面的工作量都显著地减少了。我们将必须编写的代码行数减少了一半,并大大缩短了开发周期。我们取得这些成果,很大程度上是因为联邦 DBMS 使我们能够使用新的 CMP 实体 EJB 数据建模功能:能够构建其属性涉及多个数据源的单个实体 EJB。

由于我们的工作成果,使得我们对于联邦 DBMS 技术能够给需要从多个数据源集成数据的 Web 组件开发人员带来的潜在好处感到乐观。我们鼓励具有此类业务需求的企业研究 WebSphere 和 DB2 Information Integratore 的组合能够如何帮助它们加速此类开发工作和减少未来的维护需求。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130347/,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2018-08-23

  • 博文量
    669
  • 访问量
    460122