跨多个数据源的J2EE开发:细节探讨

引言

在我们以前的文章 将 DB2 Information IntegraTor 用于 J2EE 开发:成 本/效益分析中,我们描述了一个 Java™ 2 企业版(J2EE)项目,它被设计成用来 评估实现需要从三种不同的数据源检索和合并数据的 servlet 时的折中方案。我们构建了 一组 servlet,它们使用 IBM® DB2® Information IntegraTor 来模拟不同数据 的单点映像,并且我们构建了另一组 servlet,它们直接使用每个必要的数据源。

在本文(我们系列中的第二篇)中,我们将完整地向您讲述我们开发工作的细节。 我们将回顾我们的设计、向您展示我们的部分代码并描述我们在实现这些 servlet 时所面 临的挑战。在我们的下一篇文章(我们系列中的最后一篇)中,我们将讨论性能问题。

快速回顾

如果您没有机会阅读我们的 上一篇文章,我们将在这里对它作一 下回顾。然而,我们强烈建议您回顾一下这篇文章,因为它提出了我们想要实现的应用方 案、描述了数据分布方案并说明了我们的每个 servlet 发出的查询。

我们的两组 servlet 都使用存储在 Oracle 表、DB2 表和 Microsoft® Excel 电子表格中的数据 。这些数据基于一个部件分销的数据库,它跟踪部件、供应商、客户和订单等。

我 们发现可以使用两种软件体系结构(即,使用 DB2 Information IntegraTor 或使用直接 数据访问)中的任一种来成功地构建使用这些不同数据的 servlet。然而,我们也发现:

当我们使用 DB2 Information IntegraTor 时,我们减少了大约 40% 的 servlet 代码和至少 50% 的开发时间。

当我们使用 DB2 Information IntegraTor 时,我 们不必处理复杂的查询分解问题。如果这些问题处理不当将导致错误的结果或糟糕的性能 ,而 DB2 Information IntegraTor 的全局查询优化器能够自动处理这些问题。

使用 DB2 Information IntegraTor 的 servlet 的查询响应时间通常与我们观察到的 直接数据访问实现的响应时间不相上下。有时候,我们甚至观察到使用 DB2 Information IntegraTor 时性能更好。就我们所期望的 J2EE 开发者对该环境的了解程度而言,值得注 意的是我们可以做一些工作来使直接数据访问的方案很好地被执行。

总体的体系 结构和设计

我们的 servlet 用 JDBC 通过 DB2 Information IntegraTor 来访问 数据或直接访问每个数据源。只要可能,我们就使用 DataSource 连接(合用连接),它 通常能更有效地利用系统资源。我们通过在每个 servlet 的 init方法中对这些 DataSource 执行 JNDI 查询来进一步寻求保持代码效率。

当通过 DB2 Information IntegraTor 访问远程数据时,我们的 servlet 对跨所有必要数据源的昵称 和昵称的 UNION ALL 视图发出查询。当直接访问远程数据时,我们的 servlet 对每个数 据源分别发出查询,并且将结果集插入本地 DB2 实例中的辅助表,然后查询这些表以得到 最后的结果。这极大地简化了我们的编程任务,因为另一方面,在我们应用程序空间中对 所有数据进行组合、排序、聚集和分组需要相当大的工作量。所以我们反过来选择利用本 地的 DBMS 表来帮助我们完成工作,因为根据我们的判断,DBMS 对大多数 WebSphere® 开发者是可用的。注意,我们试图在远程数据源上过滤掉尽可能多的数据 来最小化网络流量并进一步提高我们的 servlet 的效率,这些 servlet 直接使用每个数 据源。

我们的所有 servlet 的表示逻辑是简单的。我们使用 HTML 或 JSP 来获取 我们 servlet 的输入参数并发布结果。

完成所有设置

在我们编写 servlet 以前,我们必须设置我们的环境。幸运的是,这些工作并不难。

对于所有的工作,我们按标准过程安装和配置 WebSphere Studio V5 的 Application Developer 配置。我们也在相同的机器上同样按照 DB2 Information IntegraTor 的标准 配置过程来安装 DB2 Information IntegraTor。对一组 servlet,我们使用 DB2 Information IntegraTor 的联邦技术来处理不同的数据。对于另外一组 servlet,我们仅 将 DB2 Information IntegraTor 用作本地数据库存储管理器。也就是说,在我们的 servlet 手工地连接到每个必要的源并从该源检索数据以后,我们用 DB2 Information IntegraTor 来为我们的 servlet 存放辅助表。

配置 DB2 Information IntegraTor

要使用 DB2 Information IntegraTor 的联邦功能,我们安装必备软件(按照产品手册 中的定义)来访问目标数据源。然后我们创建必要的联邦数据对象,包括每个目标数据源 的包装器、每个必需的服务器实例的服务器对象、用于远程认证的用户映射以及远程数据 的昵称。

在大多数公司中,通常由数据库管理员而不是 J2EE 程序员来完成这些工作,而且 DB2 Information IntegraTor 提供了一个 GUI 工具来简化这个过程。然而,我们选择编写通 过命令行处理器发出 SQL 语句的脚本。这里是一些 SQL 语句,我们发出这些 SQL 语句来 配置对 Oracle DBMS 的访问权。在脚本编制环境中,两个连字符(“–”)表示注释。

-- Create the Oracle wrapper object.CREATE WRAPPER net;-- Create the Oracle server object.-- We need to specify a previously defined wrapper object-- and identify valid account information for our Oracle database.-- We also need to specify the node where the Oracle instance is running.CREATE SERVER jaqora TYPE oracle VERSION 8 WRAPPER net8 AUTHORIZATION "demo"PASSWORD "cdidemo" OPTIONS (NODE 'iidemo2');-- Create a user ID mapping between a valid DB2 Information IntegraTor-- account ("db2admin") and a valid Oracle account ("demo").CREATE USER MAPPING FOR db2admin SERVER jaqora OPTIONS (REMOTE_AUTHID 'demo',REMOTE_PASSWORD 'cdidemo');-- Create a local DB2 Information IntegraTor nickname("ora_part")-- for a remote Oracle table ("jaqora.demo.part").CREATE NICKNAME ora_part FOR jaqora.demo.part;

有时我们需要更改 DB2 Information IntegraTor 执行的缺省的数据类型映射。例如, 在缺省情况下,DB2 Information IntegraTor 将 Oracle 表中的数字数据映射为 DB2 DOUBLE 数据类型,但是这并不总是适合于我们的工作。更改缺省的映射很简单;我们只要 改变我们的昵称定义,如以下所示:

-- Represent the "p_partkey" column of the "ora_part" table as an integer-- locallyALTER NICKNAME ora_part altER COLUMN p_partkey LOCAL TYPE integer;

我们执行类似的语句来配置对远程 DB2 服务器和本地 Excel 电子表格(我们用 DB2 Information IntegraTor 的 ODBC 包装器通过 JDBC/ODBC 桥来访问它)的访问权。

最后,我们的某些查询需要使用跨我们三个数据源的昵称的 UNION ALL 视图。我们通 过从相关的昵称选择所有列并添加一个服务器属性列来创建这些视图,该服务器属性列用 来跟踪最初存放这些数据的数据源。这个添加的列帮助我们保留某些查询的正确的语义, 我们将稍后说明这一点。以下是我们的视图定义之一:

-- Create a consolidated view of PART data sTored in a Excel, Oracle, and DB2CREATE VIEW fed_part AS  SELECT db2_part.*, 'db2' AS p_server  FROM db2_partUNION ALL  SELECT ora_part.*, 'ora' AS p_server  FROM ora_partUNION ALL  SELECT odbc_part.*, 'xls' AS p_server  FROM odbc_part;

如果您想要回顾作为我们系统配置工作的一部分而发出的 SQL 语句,请参阅 附录 A。 如果您觉得所有这些显得太繁复了,那么请您记住:您可以使用可生成所有这些 DDL 的 GUI。

配置直接的数据访问环境

对于直接的数据访问,我们也需要安装适当的必备软 件。这些软件随所涉及的数据源的不同而不同,我们可以查阅特定于供应商的产品文档来 完成这些工作。

当连接能够正常工作时,我们创建本地 DB2 表来存储将从我们的 查询返回的中间结果。这些表随所涉及的查询的不同而不同。我们必须考虑计划如何分解 每个目标数据源的原始查询,并且确保我们创建的辅助表能够容纳返回的结果。我们总是 为每个查询创建至少一个辅助表(在某些情况下,我们创建多个辅助表)。在数据库管理 员和 J2EE 程序员合作并理解了该程序员的需求以后,很可能由数据库管理员来创建这些 对象。

在了解了您要在 DB2 中创建的表的结构以后,这个过程就简单了。我们选 择从命令行处理器发出 SQL 语句。以下是我们要创建的辅助表(它存放我们第一个查询的 中间结果)的一个例子:

create table temp_query1 (   p_partkey  integer,  p_name    varchar(55),  p_mfgr    char(25),  p_type    varchar(25))

如果您确实要了解每个 辅助表的定义,请参阅 附录 B。

管理连接

在试图发出查询以前,我们所 有的 servlet 需要建立到相应数据源的连接。只要可能,我们就使用 WebSphere 合用连 接(DataSource 对象),因为这些连接会改善系统的效率。已经证实,对于使用 DB2 Information IntegraTor 来访问数据的 servlet,我们可以方便地完成这些工作。对于直 接访问每个数据源的 servlet,我们发现只能使用 WebSphere 合用连接来访问我们的远程 DB2 Universal Database™(UDB)和 Oracle 数据源。对于我们的 Excel 数据,我 们必须依赖于由用户管理的连接(JDBC 1.0 类型的连接),等一下我们会讨论其原因。

通过 DB2 Information IntegraTor 建立连接

当使用 DB2 Information IntegraTor 时,我们的 servlet 只建立一个连接(连接到 DB2 Information IntegraTor 服务器)。只要这些 servlet 在查询中引用一个昵称(或 涉及昵称的一个视图),DB2 Information IntegraTor 就会自动地建立到不同数据源所需 要的连接以便能检索相应的数据。所有这些对我们是透明的。另外,因为 DB2 Information IntegraTor 对于 WebSphere Studio 就像是一个标准的 DB2 UDB V8 服务器 ,所以我们只需使用内置的向导定义一个 DataSource 对象。

这里是我们的一个 servlet(它是使用 DB2 Information IntegraTor 的 servlet 之 一)中的连接代码样本:

// set connection parametersprivate String jndiname = "jdbc/db2loc";private String user = "db2admin";private String pwd = "db2admin";// connection variables...private Connection conn = null;private InitialContext ctx = null;private DataSource ds = null;// look up data source.  This is called from the servlet's "init" method.private void getDS() {   System.out.println("> Jdbc2fed -- looking up datasource");   try {     ctx = new InitialContext();     if(ds == null) ds = (DataSource)ctx.lookup(jndiname);   ctx.close();   } catch (Exception e) {     System.out.println("Could not look up datasource: " + e);   return;   }}// get connectionsprivate void getConn() {   System.out.println("> Jdbc2fed -- setting up connection");   try {     conn = ds.getConnection(user, pwd);     conn.setAutoCommit(true);     ...   } catch (Exception e) {     System.out.println("Error establishing connection: " + e);   return;}

通过本机数据访问来建立连接

当我们创建直接访问每个目标数据源的 servlet 时,我们要做更多的工作。当然,我 们必须为每个数据源建立连接。我们创建 DataSource 对象以支持到远程 DB2 UDB 和 Oracle 数据的合用连接。我们也创建了一个 DataSource 对象以支持到本地 DB2 Information IntegraTor 服务器的合用连接,我们仅用该服务器来存储到每个后端数据源 的查询返回的中间结果。这意味着,我们按照标准的 WebSphere Studio 开发过程总共创 建了三个 DataSource 对象。

不幸的是,我们不能为 Excel 电子表格创建 DataSource 对象。我们的 JDBC/ODBC 桥(它提供我们需要的 Excel 访问功能)不支持对该对象的创建。所以,我们的 servlet 回过来使用 JDBC 1.0 类型的连接(由用户管理的连接)来访问 Excel 数据。这使得我们 要在 servlet 中维护不同的数据库连接类型。这虽然不难,但是这确实需要一些额外的编 码和思考。

难怪我们的连接管理代码的大小增加了。现在我们要建立(并且在以后释放)四个连接 :三个合用连接和一个由用户管理的连接。只要可能,我们使用在数组上迭代的循环来压 缩代码库大小并改善以后的代码维护。

这里摘录了我们为一个 servlet(它是直接访问每个数据源的 servlet 之一)编写的 一些连接管理代码:

// connection dataprivate static String[] dbname = new String[4];private static String[] jndiname = new String[4];private static String[] user = new String[4];private static String[] pwd = new String[4];private static String driver = "";private static String url = "";// connection objectsprivate static DataSource[] ds = new DataSource[4];private InitialContext ctx = null;private Connection[] conn = new Connection[4];public void init() {// ---------------- local db2 (host for temp data) ----------   dbname[0]= "db2_loc";   jndiname[0] = "jdbc/db2loc";   user[0] = "db2admin";   pwd[0] = "db2admin";   ...// ------------------------- DB2 remote ---------------------   dbname[1]= "db2_rem";   jndiname[1]= "jdbc/db2rem";   user[1]= "demo";   pwd[1]= "db2pass";   ...// ------------------------ Oracle remote -------------------   dbname[2]= "ora_rem";   jndiname[2] = "jdbc/ora";   user[2]= "demo";   pwd[2]= "cdidemo";// ------------------------ Excel local ----------------------// (fall back to JDBC1 connection type as JDBC-ODBC bridge does// not support JDBC2 style. connections)   dbname[3]= "xls_loc";   driver= "sun.jdbc.odbc.JdbcOdbcDriver";   url= "jdbc:odbc:ExcelJDBC";   user[3]= "";   pwd[3]= "";   ...// perform. the data source lookups.   ...}// lookup data sources. This is called by the servlet's "init" method.private void getDS() {   System.out.println("> Jdbc2Oem -- looking up datasources...");   ...   try {     ctx = new InitialContext();     for (int i = 0; i Jdbc2Oem -- setting up connections...");   // establish connections to local DB2 Information IntegraTor,   // remote DB2 UDB, remote Oracle   try {     for (int i = 0; i < 3; i++) {       conn[i] = ds[i].getConnection(user[i], pwd[i]);       conn[i].setAutoCommit(true);     }     // load the Excel driver & establish a connection     Class.forName(driver);     conn[3] = DriverManager.getConnection(url, user[3], pwd[3]);     conn[3].setAutoCommit(true);   } catch (Exception e) {     System.out.println("Error connection: " + e);   return;   }}

设计和分解查询

开发检索数据的查询显然是我们工作中最具挑战性的部分。这项任务涉及到确定实现目 标查询的正确语法和语义。(如果您已经忘了它们,请参阅 附录 C。)

当我们通过 DB2 Information IntegraTor 来访问数据时,很容易就能找到如何正确设 计每个查询的方法。实际上,您会看到我们发出的查询非常象原始的目标查询。

然而,当我们使用直接数据访问来实现这些同样的查询时,事情马上变得复杂起来。我 们必须找到如何正确分解每个查询的方法,以便从每个数据源中检索到正确的数据,同时 保持合理的性能。然后我们必须设计在中间结果上操作的“最终”查询。要使所有的工作 不出差错,说得容易做起来难。当然,这些工作的副作用是,我们需要设计、开发和维护 很多额外的代码。

好奇吗?那么就让我们从使用 DB2 Informcomparetion IntegraTor 的 servlet 方案 开始比较一下工作量吧。

开发使用 DB2 Information IntegraTor 来访问数据的查询

使用 DB2 Information IntegraTor,我们将每个目标查询转换为一个和环境相适应的 SQL 语句。在某些情况下,只需要作很少(如果有的话)的修改。在所有情况下,我们不 必为处理不同的 SQL 方言而担心(即使我们的数据分布在不同的数据源中),而且我们不 必知道数据的物理位置。DB2 Information IntegraTor 会自动解决这些问题。

查询 1 看上去和我们原始的目标查询非常相似,除了将参数标记转换为一个特定的 VARCHAR 大小。这是一种性能调节机制,我们将在我们系列的下一篇文章中讨论它的使用 。在使用 DB2 Information IntegraTor 的 servlet 中,我们如下定义第一个查询:

query[1] = "SELECT DISTINCT p_partkey, p_name, p_mfgr, p_type " +       "FROM fed_part " +       "WHERE p_type LIKE CAST(? AS VARCHAR(25)) " +       "AND p_name LIKE CAST(? AS VARCHAR(55)) " +       "ORDER BY p_partkey " +       "FETCH FIRST 20 ROWS ONLY";

查询 2 看上去也和原始的查询几乎完全相同。我们只是为添加到我们的 UNION ALL 视 图的服务器属性列增加了连接谓词。正如在前面我们已经提到,我们在 Excel、Oracle 和 DB2 数据的昵称上创建 UNION ALL 视图,并且添加一个服务器属性列来指示数据的原始来 源。我们这样做是因为我们的数据分布方式模拟了由三个独立(并在以后合并)的公司维 护的订单记录。从而,我们知道可能有一些客户对不止一个原来的公司下订单,导致重复 的 CUSTOMER 数据,但是我们也知道在相同的日期中,一个特定的客户不会对两个不同的 公司下同样的订单。通过用视图定义维护一个服务器属性列,我们可以确保取回正确的信 息 - 即,我们可以消除对我们的业务情况没有意义的跨地点连接。

以下是查询 2 的实现

query[2] ="SELECT ps_partkey, s_name, s_suppkey, " +  "MIN(ps_supplycost) AS ps_supplycost " +  "FROM fed_partsupp, fed_supplier, fed_nation " +  "WHERE ps_partkey = ? " +  "AND ps_suppkey = s_suppkey " +  "AND s_nationkey = n_nationkey " +  "AND n_name = ? " +  "AND ps_server = s_server " +  "AND s_server = n_server " +  "GROUP BY ps_partkey, s_name, s_suppkey";

查询 3、4 和 5 事实上和它们的原始版本没有什么不同。我们只是用参数标记替换了 搜索谓词值,而且对于查询 3,同样也是在视图中的服务器属性列上增加连接谓词:

query[3] = "SELECT c_custkey, c_name, SUM(o_totalprice) AS total_ordered, " +   "COUNT(*) AS num_orders " +   "FROM fed_customer, fed_orders " +     "WHERE o_custkey = c_custkey " +     "AND o_orderdate >= ? " +     "AND o_orderdate ? " +   "AND n_name in ('JAPAN', 'CHINA', 'VIETNAM', 'INDIA')";query[5] = "SELECT AVG(o_totalprice) AS avg_order, c_name, c_custkey, " +   "c_acctbal " +   "FROM db2_customer, fed_orders, db2_nation " +   "WHERE c_custkey = o_custkey " +   "AND c_nationkey = n_nationkey " +   "AND n_name = ? " +   "AND c_mktsegment = ? " +   "AND c_acctbal >>= ? " +   "AND c_acctbal <= ? " +   "GROUP BY c_custkey, c_name, c_acctbal " +   "ORDER BY avg_order DESC";

开发使用直接数据访问的查询

当我们直接使用每个数据源时,查询开发任务变得更加艰苦了。

查询 1 很简单,仅涉及 PART 数据。即使这样,我们还是必须写四条 SELECT 语句、 一条 INSERT 语句和一条 DELETE 语句来完成我们的工作。如果您仔细观察从每个后端数 据源检索数据的三条 SELECT 语句,您会看到由于 SQL 方言的差异,这三条语句略有不同 。尤其请注意 DB2 UDB 和 Oracle 在支持检索结果集的前 20 行方面是如何的不同。也要 注意对于 Excel 这项功能不可用。

最后,我们将提到取消限制每个数据源返回的结果集的大小并不总是对的。它对这个查 询的语义没有损害,但是对查询 3 的语义却有损害,我们将在稍后讨论这些。

以下是使用直接数据访问来支持查询 1 的 servlet 的 SQL 代码:

// ------------------------- DB2 remote -------------------------// select for query #1query1[1]= "SELECT DISTINCT p_partkey, p_name, p_mfgr, p_type " +  "FROM tpcd.part " +  "WHERE p_type LIKE ? " +  "AND p_name LIKE ? " +  "ORDER BY p_partkey " +  "FETCH FIRST 20 ROWS ONLY";// ------------------------ Oracle remote -----------------------// select for query #1query1[2]= "SELECT * FROM (" +  "SELECT p_partkey, p_name, p_mfgr, p_type " +  "FROM part " +  "WHERE p_type LIKE ? " +  "AND p_name LIKE ? " +  "ORDER BY p_partkey" +  ") WHERE ROWNUM <= 20";// ------------------------ Excel local -----------------------// select for query #1query1[3]= "SELECT p_partkey, p_name, p_mfgr, p_type " +  "FROM [part$] " +  "WHERE p_type LIKE ? " +  "AND p_name LIKE ?";// insert statement for intermediate data for query #1temp_insert[1]= "INSERT INTO temp_query1(p_partkey, p_name,p_mfgr, p_type) "   + "VALUES(?, ?, ?, ?)";// final statement for query #1temp_select[1]= "SELECT DISTINCT * " +  "FROM temp_query1 " +  "ORDER BY p_partkey " +  "FETCH FIRST 20 ROWS ONLY";// delete statements for temporary tabletemp_delete[1] = "DELETE FROM temp_query1";

查询 2 没有任何特别的问题。从概念上讲,它的实现和查询 1 几乎相同。我们必须写 四条 SELECT 语句、一条 INSERT 语句和一条 DELETE 语句来完成我们的工作。对于其中 的三条 SELECT 语句,我们实际上取用查询 2 的原始版本、用参数标记替换硬编码搜索值 、修改 Excel 数据的 FROM 子句的语法之后这些语句就可以执行了。INSERT 语句只是从 每个数据源取得结果并将它们存放到本地辅助表。我们的第四条 SELECT 语句对该表执行 操作并且该语句的语法是原始查询简略版本。(我们省略了连接和搜索谓词,因为它们已 经在前三条 SELECT 语句中应用了。)最后,我们发出一条简单的 DELETE 语句来清除辅 助表中的内容。

为了保证完整性,我们在下面列出了我们的 servlet 中查询 2 的 SQL 代码。

// ------------------------- DB2 remote -------------------------// select for query #2query2[1]= "SELECT ps_partkey, s_name, s_suppkey, MIN(ps_supplycost)AS min_supplycost " +  "FROM tpcd.partsupp, tpcd.supplier, tpcd.nation " +  "WHERE ps_partkey = ? " +  "AND ps_suppkey = s_suppkey " +  "AND s_nationkey = n_nationkey " +  "AND n_name = ? " +  "GROUP BY ps_partkey, s_name, s_suppkey";// ------------------------ Oracle remote ------------------------// select for query #2query2[2]= "SELECT ps_partkey, s_name, s_suppkey, MIN(ps_supplycost)AS min_supplycost " +  "FROM partsupp, supplier, nation " +  "WHERE ps_partkey = ? " +  "AND ps_suppkey = s_suppkey " +  "AND s_nationkey = n_nationkey " +  "AND n_name = ? " +  "GROUP BY ps_partkey, s_name, s_suppkey";// ------------------------ Excel local ---------------------------// select for query #2query2[3]= "SELECT ps_partkey, s_name, s_suppkey, MIN(ps_supplycost)AS min_supplycost " +  "FROM [partsupp$], [supplier$], [nation$] " +  "WHERE ps_partkey = ? " +  "AND ps_suppkey = s_suppkey " +  "AND s_nationkey = n_nationkey " +  "AND n_name = ? " +  "GROUP BY ps_partkey, s_name, s_suppkey";// insert statement for intermediate data for query #2temp_insert[2]= "INSERT INTO temp_query2(ps_partkey, s_name, " +  "s_suppkey,   min_supplycost) " +  "VALUES(?, ?, ?, ?)";// final statement for query #2temp_select[2]= "SELECT ps_partkey, s_name, s_suppkey, MIN(min_supplycost) AS ps_supplycost " +  "FROM temp_query2 " +  "GROUP BY ps_partkey, s_name, s_suppkey";// delete statements for temporary tablestemp_delete[2] = "DELETE FROM temp_query2";

查询 3 是更有趣的情况。可能您还记得,该查询设计成在一定时间段内确定最佳的 10 个客户 - 即,在某个时间段内向我们的合并公司订货金额最高的客户。如果我们对一个 单独的表(或 DB2 Information IntegraTor 提供的一个单独的虚拟表)发出该查询,我 们累计每个客户的所有订单、按降序返回结果并将返回的行数限制为 10。

但是当我们直接对不同的数据源提出这个问题时,事情变得更加复杂了。我们不能只发 出基本查询而使其 ORDER BY 子句和结果大小限制保持原样。为什么?这是因为(如果这 样做)我们可能得到不正确的结果。如果一个客户向我们合并前的三个公司分别下了订单 ,这些订单不能列入每个公司的前 10 位,但是当它们加起来产生的总和却排到合并后公 司的前 10 位,这种情况下怎么办?而我们不想遗漏这些客户。要确保我们不遗漏这些客 户唯一的方法是,我们从每个数据源检索所有满足条件的行并在合并(中间)的结果集上 应用 ORDER BY 和大小限制子句。

当然,如以下代码说明的那样,我们就是这样做的。我们必须考虑好原始查询的语义以 确保没有违背我们的初衷,但是这并不是那么明显的。仅把整个查询按照其原始的状态在 每个目标数据源上执行,这本是很容易而且诱人的。毕竟这对查询 1(它也有结果集大小 限制)是可行的。但是在这里行不通,其原因我们刚才已经说明。要说明其中的区别需要 时间、精力和技巧 - 也许比马上就能感觉到的还要多。

// ------------------------- DB2 remote -------------------------// select for query #3query3[1]= "SELECT SUM(o_totalprice) AS total_ordered, COUNT(*) ASnum_orders, c_custkey, c_name " +  "FROM tpcd.customer, tpcd.orders " +  "WHERE o_custkey = c_custkey " +  "AND o_orderdate >= ? " +  "AND o_orderdate = ? " +  "AND o_orderdate = ? " +  "AND o_orderdate < ? " +  "GROUP BY c_custkey, c_name ";// insert statement for intermediate data for query #3temp_insert[3]= "INSERT INTO temp_query3(c_custkey, c_name, total_ordered,num_orders) " +  "VALUES(?, ?, ?, ?)";// final statement for query #3temp_select[3]= "SELECT c_custkey, c_name, SUM(total_ordered) AStotal_ordered, SUM(num_orders) AS num_orders " +  "FROM temp_query3 " +  "GROUP BY c_custkey, c_name " +  "ORDER BY total_ordered DESC " +  "FETCH FIRST 10 ROWS ONLY";// delete statements for temporary tablestemp_delete[3] = "DELETE FROM temp_query3";

查询 4 和 5 与前三个查询在某些重要的方面是不同的。前三个查询只对三个数据源的 数据的昵称的 UNION ALL 视图进行操作。查询 4 执行跨地点连接;也就是说,它处理由 远程 DB2 UDB 系统管理的某些数据和远程 Oracle 系统管理的某些数据。它不使用 UNION ALL 视图。查询 5 使用了这样的一个视图,但是将该视图和特定于某个数据源的数据相连 接。这些查询的性质迫使我们修改我们的 servlet 逻辑。

我们将查询 4 分成两段:一段包含特定于远程 DB2 服务器的所有搜索谓词,另外一段 包含特定于远程 Oracle 服务器的所有搜索谓词。我们为每段构造 SELECT 语句,对远程 DB2 UDB 和 Oracle 数据库执行这些语句,并将结果插入我们本地 DB2 系统的辅助表。然 后我们发出最后的 SELECT 语句,该语句连接需要满足我们原始查询的辅助表。

如果您做过很多的 DBMS 应用程序编程工作,那么您知道这是我们能编写 servlet 逻 辑的唯一方法。我们考虑过其它可选的方法,例如在我们的 servlet 中编写等效的嵌套循 环,但是我们不信这里面有全胜的赢家。如果我们预先知道 DB2 UDB 或 Oracle 查询都将 返回一个很小的结果集,那么我们可以推断,如果我们使用这些结果进一步限制其它数据 源返回满足条件的行的数目,那么将改善性能。

但是我们无法知道这些。我们无法访问全局统计信息,而且我们不能假定 J2EE 开发者 熟悉数据分布方案或底层的业务活动(它们引起数据这样或那样的偏离)。此外,我们的 servlet 使用参数标记来容纳用户可能输入的不同搜索值。这进一步禁止我们预先知道从 任一远程数据源返回的结果集会很小。

因而,我们必须有些盲目地选择一个合理的实现。此外,值得注意的是,即使我们以某 种方式知道(或正确地猜测到)结果集的大小,我们也还是不能保证修改过的实现经过一 段时间后性能不会降低。毕竟,在远程 Oracle 和 DB2 UDB 系统中的数据可能会改变;结 果,以前运行得很好的 servlet 代码可能工作得不再理想了。

使用 DB2 Information IntegraTor,我们不必担心数据访问的策略,因为系统的全局 优化器负责分析不同的访问途径并选择有效率的一个。

以下摘录了我们的 servlet 中包含的查询 4 的 SQL 代码:

// statements for query #4q2db2fetch = conn[1].prepareStatement("SELECT c_custkey, c_name, n_name " +   " FROM tpcd.customer, tpcd.nation " +   " WHERE c_nationkey = n_nationkey " +   " AND n_name IN ('JAPAN', 'CHINA', 'VIETNAM', 'INDIA')");q2orafetch = conn[2].prepareStatement("SELECT o_custkey, o_totalprice " +   " FROM orders WHERE o_totalprice > ? ");q2insertfromdb2 = conn[0].prepareStatement("INSERT INTO temp_jquery2a " +   " (c_custkey, c_name, n_name) VALUES (?, ?, ?)");q2insertfromora = conn[0].prepareStatement("INSERT INTO temp_jquery2b " +   " (o_custkey, o_totalprice) VALUES (?,?)");q2tempselect = conn[0].prepareStatement("SELECT c_custkey, c_name, " +   " o_totalprice, n_name " +   " FROM temp_jquery2a, temp_jquery2b " +   " WHERE c_custkey = o_custkey");q2tempdelete1 = conn[0].prepareStatement("DELETE FROM temp_jquery2a");q2tempdelete2 = conn[0].prepareStatement("DELETE FROM temp_jquery2b");

最后,查询 5 结果是这几个查询中最有技巧的一个。这里我们需要找出订单的平均成 本,这些订单是一部分满足条件的客户对整个(合并)公司下的。我们不能仅仅拿出针对 每个数据源而计算的平均值;当我们跨 所有数据源来计算每个满足条件的客户的订单平均 值时,我们最终得到的结果将是不正确的。但是不能成功地将某些聚集操作叠加到每个数 据源将产生大量的数据传输,其中很多是不必要的。

相反,我们决定对每个后端数据源发出 COUNT(*) 和 SUM 函数来计算每个客户的订单 数和这些订单的总成本。通过合并这些信息(以及我们客户的其它数据),我们可以跨所 有数据源来计算每个满足条件的客户所下的所有订单的平均金额。

认识到要按这种方式来修改我们的查询以保留我们的语义和维持合理的性能需要一些思 考。但是这是事情的唯一部分。因为原始的查询按其它方式限制满足条件的客户,我们想 要利用这些增加的搜索谓词来进一步最小化不必要的数据传输。

最后,我们为远程 DB2 UDB 编写了一个非常具有限制性的查询,来获取所有满足条件 的客户和有关订单数量信息以及他们所下订单的总成本信息。这些信息放入一个辅助表。 然后我们能够使用此表中的唯一客户标识(CUSTKEY 信息)来进一步限制从我们的 Oracle 和 Excel 系统返回的结果。如果我们在那里找到任何匹配的行,那么我们对每个客户计算 订单的总数和这些订单的总成本,并更新辅助表中的数据来反映这一点。然后我们能够使 用这些数据来计算出我们需要的信息。

所有这些工作具有类似于在我们的 servlet 加入嵌套循环连接所具有的实际效果。最 终我们有五条 SELECT 语句、一条 INSERT 语句、一条 UPDATE 语句和一条 DELETE 语句 。将这些和我们为这个查询的 DB2 Information IntegraTor 版本所发出的单个 SELECT 语句进行比较,您对这种方法所增加的成本会有所了解。当然,这还没有考虑我们为了正 确执行这些查询并得到正确结果所必须设计和实现的额外逻辑。

以下是我们从使用直接数据访问来实现查询 5 的 servlet 中抽取的 SQL 代码:

// statements for query #5q3db2fetch = conn[1].prepareStatement("SELECT COUNT(*) AS num_order, " +   " SUM(o_totalprice) AS total_order, c_name, c_custkey, c_acctbal " +   " FROM tpcd.customer, tpcd.orders, tpcd.nation " +   " WHERE c_custkey = o_custkey " +   " AND c_nationkey = n_nationkey " +   " AND n_name = ? AND c_mktsegment = ? " +   " AND c_acctbal >= ? AND c_acctbal <= ? " +   " GROUP BY c_custkey, c_name, c_acctbal + +   " ORDER BY total_order, num_order");q3orafetch = conn[2].prepareStatement("SELECT COUNT(*) AS num_order, " +   " SUM(o_totalprice) AS total_order, o_custkey " +   " FROM orders WHERE o_custkey = ? " +   " GROUP BY o_custkey");q3xlsfetch = conn[3].prepareStatement("SELECT COUNT(*) AS num_order, " +   " SUM(o_totalprice) AS total_order, o_custkey " +   " FROM [orders$] WHERE o_custkey = ? " +   " GROUP BY o_custkey");q3tempfetch = conn[0].prepareStatement("SELECT c_custkey FROM temp_jquery3");q3insertfromdb2 = conn[0].prepareStatement("INSERT INTO temp_jquery3 " +   " (c_custkey, c_name, c_acctbal, total_order, num_order) " +   " VALUES (?, ?, ?, ?, ?)");q3update = conn[0].prepareStatement("UPDATE temp_jquery3 " +   " SET num_order = num_order + ?, total_order = total_order + ? " +   " WHERE c_custkey = ?");q3tempselect = conn[0].prepareStatement("SELECT AVG(total_order/num_order) " +   " AS avg_order, c_name, c_custkey, c_acctbal " +   " FROM temp_jquery3 " +   " GROUP BY c_custkey, c_name, c_acctbal " +   " ORDER BY avg_order DESC");q3tempdelete = conn[0].prepareStatement("DELETE FROM temp_jquery3");

执行查询

如果您还在,恭喜您。我们知道这需要了解很多东西。但是您几乎要通过最后一关了。 我们只想简要地谈谈用什么来执行我们在上一节展示的这些查询。

同样的,您可能差不多预料到了这种情况:使用 DB2 Information IntegraTor 的 servlet 只需要做很少的工作,而直接访问每个数据源的 servlet 要做多得多的工作。我 们不会在这里一个接着一个地向您展示查询,因为很多代码是重复的。相反,我们只是首 先从 DB2 Information IntegraTor 开始讨论一些有趣的案例。

使用 DB2 Information IntegraTor

每个为支持 DB2 Information IntegraTor 所写的 servlet 只需要一种方法来执行其 支持的任一查询。(根据我们的项目的性质,我们在一个 servlet 中实现查询 1 到 3, 并在另一个 servlet 中实现查询 4 和 5。我们还有两个 servlet 来实现直接数据访问: 一个支持查询 1 到 3,另一个则支持查询 4 和 5)。用户输入使我们的 servlet 能够确 定要执行它所支持的哪个查询。

当对我们的查询语句对象应用了参数标记后,我们执行了语句并返回结果:

  stmt[choice].execute();  return outputResult(choice); 

对于我们的两个 DB2 Information IntegraTor servlet 中的 executeQuery() 方法来 说,此代码是完全相同的。

使用直接数据访问

对每个查询使用不同的方法,为直接访问每个数据源所写的 servlet 是最容易实现的 。这是因为每个查询所返回的中间结果集都是不同的,这些结果需要插入到辅助表中来进 行进一步处理。

当对我们的查询语句对象应用了参数标记后,我们写了如下代码来执行查询 1:

ResultSet rs = null;int rows = 0;...// fetch the data from each data source and insert it into temp tablefor(int i = 1; i " + rows + " rows inserted from " + dbname [i]);}if (rs != null) { rs.close(); }return outputResult(1);

执行查询 2 和 3 的逻辑与之非常相似,因此我们不在这里重复了。查询 4 和 5 的性 质有很大不同,并且查询 5 肯定是所有查询中最困难的。为了使您了解我们需要进行什么 样的编码,以下是对执行查询 5 的 servlet 方法的节选。同样地,我们已经除去了读取 用户的输入参数并设置这些值的代码。

// code for query #5 executionprivate String executeQuery3(HttpServletRequest req)throws SQLException,ParseException {   ResultSet rs = null;   int rows = 0;   // read the parameters   ...   // insert from DB2   q3db2fetch.execute();   rs = q3db2fetch.getResultSet();   while (rs.next() ) {     Q3insertfromdb2.setInt(1,new   Double(rs.getString("c_custkey")).intValue());     q3insertfromdb2.setString(2, rs.getString("c_name"));     q3insertfromdb2.setDouble(3, newDouble(rs.getString("c_acctbal")).doubleValue());     q3insertfromdb2.setDouble(4, newDouble(rs.getString("total_order")).doubleValue());     q3insertfromdb2.setInt(5, newDouble(rs.getString("num_order")).intValue());     q3insertfromdb2.executeUpdate();     rows++;   }   System.out.println("rows inserted from db2: " + rows);   // for each c_custkey from temp table, select/update   from ora/odbc   q3tempfetch.execute();   rs = q3tempfetch.getResultSet();   rows = 0;   while (rs.next()) {     ResultSet rstemp = null;     // do oracle     q3orafetch.setInt(1, newDouble(rs.getString("c_custkey")).intValue());     q3orafetch.execute();     rstemp = q3orafetch.getResultSet();     if(rstemp.next()) {       q3update.setInt(1, newDouble(rstemp.getString("num_order")).intValue());       q3update.setDouble(2, newDouble(rstemp.getString("total_order")).doubleValue());       q3update.setInt(3, newDouble(rs.getString("c_custkey")).intValue());       System.out.println("update values from       ora for key " +rs.getString("c_custkey") + " / " + rstemp.getString("num_order")+ " / " +rstemp.getString("total_order"));       }     // do excel     q3xlsfetch.setInt(1, newDouble(rs.getString("c_custkey")).intValue());     q3xlsfetch.execute();     rstemp = q3xlsfetch.getResultSet();     if(rstemp.next()) {       q3update.setInt(1, newDouble(rstemp.getString("num_order")).intValue());       q3update.setDouble(2, newDouble(rstemp.getString("total_order")).doubleValue());       q3update.setInt(3, newDouble(rs.getString("c_custkey")).intValue());       System.out.println("update values from       xls for key " +rs.getString("c_custkey") + " / " + rstemp.getString("num_order")+ " / " +rstemp.getString("total_order"));     }     rows++;     System.out.println("row #" + rows + " checked");   }   System.out.println("rows checked for update by ora/xls: " + rows);   // process our final join query   q3tempselect.execute();   rs = q3tempselect.getResultSet();   // output result   String text = outputResult(rs);     if (rs != null) rs.close();   // cleanup   q3tempdelete.execute();   return text;}

比较实现成本

如果您对在我们的项目中所需要编写的代码数量感到好奇,我们可以告诉您这个数目。 我们在一组 servlet 中实现前三个查询,并在另一组 servlet 中实现查询 4 和 5。

使用 DB2 Information IntegraTor 来进行数据访问的前三个查询的 servlet 由 289 行代码组成;直接访问每个数据源的前三个查询的 servlet 由 521 行代码组成。使用 DB2 Information IntegraTor 的最后两个查询的 servlet 由 280 行代码组成,而直接访 问数据的后两个查询的 servlet 由 430 行代码组成。

当然,这不能完全反映出实现其中每个 servlet 所需的工作量。当直接处理每一个数 据源时,很难记录分解查询所需的“报文延迟时间”,而且我们经常会发现我们的设计、 编码和测试的周期是不断重复的。然而,我们通常花费大约双倍的时间才能使直接数据访 问 servlet 正常工作。对于复杂查询(例如查询 5),我们的实现阶段则须花费更长的时 间。

总结

那么,到目前为止,您应该对于我们的 servlet 是如何实现的有了一个比较清楚的了 解。当使用 DB2 Information IntegraTor 来帮助我们从三个不同的数据源访问和合并信 息时,我们发现很显然工作变得更简单,而且更快了。我们减少了大约 40% 必须实现的代 码行,并且减少了更多的实现时间。

我们节省的大部分工作量是由于能够使用 DB2 Information IntegraTor 来更加抽象地 工作。我们不需要考虑数据的位置,我们不需要确定怎样正确地分解对每个数据源的查询 ,我们无须担心数据访问策略,而且我们甚至不必费心去考虑 SQL 方言的不同。DB2 Information IntegraTor 负责为我们处理所有这些内容。

那么,我们得到了什么呢?是的,我们的确需要安装和配置一个 DB2 Information IntegraTor 环境。但是,这只是一次性工作,我们希望有一个数据库或系统管理员至少承 担了生产环境中的部分工作。但是,我们不必将就糟糕的性能,也许这与您不谋而合。请 继续关注本系列中最后一篇文章,我们将在其中讨论不同的查询实现的性能问题。

附录 A. 配置我们的 DB2 Information IntegraTor 环境的 SQL 语句

------------------------------------------------------ 1. get database connection----------------------------------------------------CONNECT TO test USER db2admin USING db2pwd;------------------------------------------------------ 2.1. DB2 remote server-- Node & authorization information here have been changedfrom our in-house environment------------------------------------------------------ catalog the nodeUNCATALOG NODE jaqdb2;CATALOG TCPIP NODE jaqdb2 REMOTE 9.99.99.91 SERVER 50000;-- catalog the databaseUNCATALOG DATABASE tpcd;CATALOG DATABASE tpcd AT NODE jaqdb2;-- create DB2 wrapperDROP WRAPPER drda;CREATE WRAPPER drda;-- create DB2 server definitionCREATE SERVER jaqdb2 TYPE db2/udb VERSION 8.1 WRAPPER drdaAUTHORIZATION "demo"PASSWORD "cdidemo" OPTIONS (DBNAME 'tpcd');-- create User mappingCREATE USER MAPPING FOR db2admin SERVER jaqdb2 OPTIONS(REMOTE_AUTHID 'demo',REMOTE_PASSWORD 'cdidemo');-- create nicknamesCREATE NICKNAME db2_part FOR jaqdb2.tpcd.part;CREATE NICKNAME db2_supplier FOR jaqdb2.tpcd.supplier;CREATE NICKNAME db2_partsupp FOR jaqdb2.tpcd.partsupp;CREATE NICKNAME db2_nation FOR jaqdb2.tpcd.nation;CREATE NICKNAME db2_region FOR jaqdb2.tpcd.region;CREATE NICKNAME db2_customer FOR jaqdb2.tpcd.customer;CREATE NICKNAME db2_orders FOR jaqdb2.tpcd.orders;------------------------------------------------------ 2.2. Oracle remote server------------------------------------------------------ create Oracle wrapperDROP WRAPPER net;CREATE WRAPPER net;-- create Oracle server definitionCREATE SERVER jaqora TYPE oracle VERSION 8 WRAPPER net8AUTHORIZATION "demo"PASSWORD "cdidemo" OPTIONS (NODE 'iidemo2');-- create user mappingCREATE USER MAPPING FOR db2admin SERVER jaqora OPTIONS(REMOTE_AUTHID 'demo',REMOTE_PASSWORD 'cdidemo');-- create nicknamesCREATE NICKNAME ora_part FOR jaqora.demo.part;CREATE NICKNAME ora_supplier FOR jaqora.demo.supplier;CREATE NICKNAME ora_partsupp FOR jaqora.demo.partsupp;CREATE NICKNAME ora_customer FOR jaqora.demo.customer;CREATE NICKNAME ora_orders FOR jaqora.demo.orders;CREATE NICKNAME ora_lineitem FOR jaqora.demo.lineitem;CREATE NICKNAME ora_nation FOR jaqora.demo.nation;-- adjust data type to match other dbmsALTER NICKNAME ora_part altER COLUMN p_partkey LOCAL TYPE   integer;ALTER NICKNAME ora_part altER COLUMN p_size LOCAL TYPE   integer;ALTER NICKNAME ora_partsupp altER COLUMN ps_partkey LOCAL TYPE   integer;ALTER NICKNAME ora_partsupp altER COLUMN ps_suppkey LOCAL TYPE   integer;ALTER NICKNAME ora_partsupp altER COLUMN ps_availqty LOCAL TYPE   integer;ALTER NICKNAME ora_supplier altER COLUMN s_suppkey LOCAL TYPE   integer;ALTER NICKNAME ora_supplier altER COLUMN s_nationkey LOCAL TYPE   integer;ALTER NICKNAME ora_nation altER COLUMN n_nationkey LOCAL TYPE   integer;ALTER NICKNAME ora_nation altER COLUMN n_regionkey LOCAL TYPE   integer;ALTER NICKNAME ora_orders altER COLUMN o_orderdate LOCAL TYPE   date;ALTER NICKNAME ora_orders altER COLUMN o_custkey LOCAL TYPE   integer;ALTER NICKNAME ora_orders altER COLUMN o_orderkey LOCAL TYPE   integer;ALTER NICKNAME ora_orders altER COLUMN o_shippriority LOCAL TYPE   integer;ALTER NICKNAME ora_customer altER COLUMN c_custkey LOCAL TYPE   integer;ALTER NICKNAME ora_customer altER COLUMN c_nationkey LOCAL TYPE   integer;------------------------------------------------------ 2.3. ODBC local data source (Excel)------------------------------------------------------ create ODBC wrapperDROP WRAPPER odbc;CREATE WRAPPER odbc LIBRARY 'db2rcodbc.dll';-- create ODBC serverCREATE SERVER jaqodbc TYPE odbc VERSION 3.0 WRAPPER odbc OPTIONS   (NODE 'ExcelDB2',DB2_NULL_VARIANT 'Y', DB2_VARIANT 'Y', PUSHDOWN 'Y');-- create nicknamesCREATE NICKNAME odbc_part FOR jaqodbc.part$;CREATE NICKNAME odbc_supplier FOR jaqodbc.supplier$;CREATE NICKNAME odbc_orders FOR jaqodbc.orders$;CREATE NICKNAME odbc_partsupp FOR jaqodbc.partsupp$;CREATE NICKNAME odbc_customer FOR jaqodbc.customer$;CREATE NICKNAME odbc_nation FOR jaqodbc.nation$;-- adjust nicknames to correct typesALTER NICKNAME odbc_part altER COLUMN p_partkey LOCAL TYPE   integer;ALTER NICKNAME odbc_part altER COLUMN p_name LOCAL TYPE   varchar(55);ALTER NICKNAME odbc_part altER COLUMN p_mfgr LOCAL TYPE   char(25);ALTER NICKNAME odbc_part altER COLUMN p_brand LOCAL TYPE   char(10);ALTER NICKNAME odbc_part altER COLUMN p_type LOCAL TYPE   varchar(25);ALTER NICKNAME odbc_part altER COLUMN p_size LOCAL TYPE   integer;ALTER NICKNAME odbc_part altER COLUMN p_container LOCAL TYPE   char(10);ALTER NICKNAME odbc_part altER COLUMN p_retailprice LOCAL TYPE   decimal(10,4);ALTER NICKNAME odbc_part altER COLUMN p_comment LOCAL TYPE   varchar(23);--ALTER NICKNAME odbc_customer altER COLUMN c_custkey LOCAL TYPE   integer;ALTER NICKNAME odbc_customer altER COLUMN c_name LOCAL TYPE   char(25);ALTER NICKNAME odbc_customer altER COLUMN c_address LOCAL TYPE   varchar(40);ALTER NICKNAME odbc_customer altER COLUMN c_nationkey LOCAL TYPE   integer;ALTER NICKNAME odbc_customer altER COLUMN c_phone LOCAL TYPE   char(15);ALTER NICKNAME odbc_customer altER COLUMN c_acctbal LOCAL TYPE   decimal(10,4);ALTER NICKNAME odbc_customer altER COLUMN c_mktsegment LOCAL TYPE   char(10);ALTER NICKNAME odbc_customer altER COLUMN c_comment LOCAL TYPE   varchar(117);--ALTER NICKNAME odbc_partsupp altER COLUMN ps_partkey LOCAL TYPE   integer;ALTER NICKNAME odbc_partsupp altER COLUMN ps_suppkey LOCAL TYPE   integer;ALTER NICKNAME odbc_partsupp altER COLUMN ps_availqty LOCAL TYPE   integer;ALTER NICKNAME odbc_partsupp altER COLUMN ps_supplycost LOCAL TYPE   decimal(10,4);ALTER NICKNAME odbc_partsupp altER COLUMN ps_comment LOCAL TYPE   varchar(199);--ALTER NICKNAME odbc_nation altER COLUMN n_nationkey LOCAL TYPE   integer;ALTER NICKNAME odbc_nation altER COLUMN n_name LOCAL TYPE   char(25);ALTER NICKNAME odbc_nation altER COLUMN n_regionkey LOCAL TYPE   integer;ALTER NICKNAME odbc_nation altER COLUMN n_comment LOCAL TYPE   varchar(152);--ALTER NICKNAME odbc_orders altER COLUMN o_orderkey LOCAL TYPE   integer;ALTER NICKNAME odbc_orders altER COLUMN o_custkey LOCAL TYPE   integer;ALTER NICKNAME odbc_orders altER COLUMN o_orderstatus LOCAL TYPE   char(1);ALTER NICKNAME odbc_orders altER COLUMN o_totalprice LOCAL TYPE   decimal(10,4);ALTER NICKNAME odbc_orders altER COLUMN o_orderdate LOCAL TYPE   date;ALTER NICKNAME odbc_orders altER COLUMN o_orderpriority LOCAL TYPE   char(15);ALTER NICKNAME odbc_orders altER COLUMN o_clerk LOCAL TYPE   char(15);ALTER NICKNAME odbc_orders altER COLUMN o_shippriority LOCAL TYPE   integer;ALTER NICKNAME odbc_orders altER COLUMN o_comment LOCAL TYPE   varchar(79);--ALTER NICKNAME odbc_supplier altER COLUMN s_suppkey LOCAL TYPE   integer;ALTER NICKNAME odbc_supplier altER COLUMN s_name LOCAL TYPE   char(25);ALTER NICKNAME odbc_supplier altER COLUMN s_address LOCAL TYPE   varchar(40);ALTER NICKNAME odbc_supplier altER COLUMN s_nationkey LOCAL TYPE   integer;ALTER NICKNAME odbc_supplier altER COLUMN s_phone LOCAL TYPE   char(15);ALTER NICKNAME odbc_supplier altER COLUMN s_acctbal LOCAL TYPE   decimal(10,4);ALTER NICKNAME odbc_supplier altER COLUMN s_comment LOCAL TYPE   varchar(101);------------------------------------------------------ 3. Union views over federated nicknames-------------------------------------------------------- for query #1DROP VIEW fed_part;CREATE VIEW fed_part AS  SELECT db2_part.*, 'db2' AS p_server  FROM db2_partUNION ALL  SELECT ora_part.*, 'ora' AS p_server  FROM ora_partUNION ALL  SELECT odbc_part.*, 'xls' AS p_server  FROM odbc_part;---- for query #2DROP VIEW fed_partsupp;CREATE VIEW fed_partsupp AS  SELECT db2_partsupp.*, 'db2' AS ps_server  FROM db2_partsuppUNION ALL  SELECT ora_partsupp.*, 'ora' AS ps_server  FROM ora_partsuppUNION ALL  SELECT odbc_partsupp.*, 'xls' AS ps_server  FROM odbc_partsupp;--DROP VIEW fed_supplier;CREATE VIEW fed_supplier AS  SELECT db2_supplier.*, 'db2' AS s_server  FROM db2_supplierUNION ALL  SELECT ora_supplier.*, 'ora' AS s_server  FROM ora_supplierUNION ALL  SELECT odbc_supplier.*, 'xls' AS s_server  FROM odbc_supplier;--DROP VIEW fed_nation;CREATE VIEW fed_nation AS  SELECT db2_nation.*, 'db2' AS n_server  FROM db2_nationUNION ALL  SELECT ora_nation.*, 'ora' AS n_server  FROM ora_nationUNION ALL  SELECT odbc_nation.*, 'xls' AS n_server  FROM odbc_nation;---- for query #3DROP VIEW fed_customer;CREATE VIEW fed_customer AS  SELECT db2_customer.*, 'db2' AS c_server  FROM db2_customerUNION ALL  SELECT ora_customer.*, 'ora' AS c_server  FROM ora_customerUNION ALL  SELECT odbc_customer.*, 'xls' AS c_server  FROM odbc_customer;--DROP VIEW fed_orders;CREATE VIEW fed_orders AS  SELECT db2_orders.*, 'db2' AS o_server  FROM db2_ordersUNION ALL  SELECT ora_orders.*, 'ora' AS o_server  FROM ora_ordersUNION ALL  SELECT odbc_orders.*, 'xls' AS o_server  FROM odbc_orders;

附录 B. 直接数据访问的辅助表定义

我们在一个本地 DB2 Information IntegraTor 数据库中创建了辅助表,来保存直接对 目标数据源(Oracle、DB2 UDB 和 Excel)执行的查询所返回的中间结果集。以下是对我 们创建的每个辅助表的定义。

------------------------------------------------------ Temporary tables for native data Access-- This assumes you already have a database connection.------------------------------------------------------ for query #1DROP TABLE temp_query1;CREATE TABLE temp_query1(p_partkey INTEGER,             p_name VARCHAR(55),             p_mfgr CHAR(25),             p_type VARCHAR(25)) DATA CAPTURE NONE;-- for query #2DROP TABLE temp_query2;CREATE TABLE temp_query2(ps_partkey INTEGER,             s_name CHAR(25),             s_suppkey INTEGER,             min_supplycost DOUBLE) DATA CAPTURE NONE;-- for query #3DROP TABLE temp_query3;CREATE TABLE temp_query3(c_custkey INTEGER,             c_name CHAR(25),             total_ordered DOUBLE,             num_orders INTEGER) DATA CAPTURE NONE;-- for joined query #4DROP TABLE temp_jquery1a;CREATE TABLE temp_jquery1a(c_custkey INTEGER,              c_name VARCHAR(55),              c_phone VARCHAR(15)) DATA CAPTURE NONE;DROP TABLE temp_jquery1b;CREATE TABLE temp_jquery1b(o_custkey INTEGER) DATA CAPTURE NONE;-- for joined query #5DROP TABLE temp_jquery2a;CREATE TABLE temp_jquery2a(c_custkey INTEGER,              c_name VARCHAR(25),              n_name VARCHAR(25)) DATA CAPTURE NONE;DROP TABLE temp_jquery2b;CREATE TABLE temp_jquery2b(o_custkey INTEGER,              o_totalprice DOUBLE) DATA CAPTURE NONE;-- for joined query #6DROP TABLE temp_jquery3a;CREATE TABLE temp_jquery3a(c_custkey INTEGER,              c_name VARCHAR(25),              c_acctbal DOUBLE) DATA CAPTURE NONE;DROP TABLE temp_jquery3b;CREATE TABLE temp_jquery3b(o_custkey INTEGER,              total_order DOUBLE,              num_order INTEGER) DATA CAPTURE NONE;-- new table for joined query #6DROP TABLE temp_jquery3;CREATE TABLE temp_jquery3(c_custkey INTEGER,              c_name VARCHAR(25),              c_acctbal DOUBLE,              total_order DOUBLE,              num_order INTEGER) DATA CAPTURE NONE;

附录 C. 目标 SQL 查询

在我们的第一篇文章中,介绍了一些我们的 servlet 需要实现的查询。这里我们也包 含了这些查询,以供参考。请注意以下所示的 SQL 语句是概念性的。例如,它们出示了特 定的搜索谓词;但在实际的实现过程中,我们使用了参数标记以使查询更加灵活。另外, 我们把它们编写得好像所有的数据都驻留在一个单一的数据库中;如您所知,我们的数据 实际上是驻留在三个不同的数据源上的。

查询 1 到 3 模拟一个在线目录搜索应用程序。前两者使用户能够找到带有某些特性的 部件,然后确定所选择的部件在某个给定的国家或地区中价格最低的供应商。第三个查询 确定该用户是否属于一段给定时间里最佳的 10 名客户之一,如果是的话,他(或她)可 能有资格得到免费送货或者打折优惠。

查询 4 和 5 支持业务分析工作,这种工作可能出现在合并后方案中。它们试图在两个 或更多新合并的公司中识别共同客户,这些客户具有一些共同的有趣的特点 - 或许他们 都具有某种目标客户统计特征、居住在竞争激烈的地域、发出了大的订单等等。

当阅读我们这里的查询时,您应该假定在 FROM 子句中引用的对象包括来自所有数据源 的数据,除非它的名称中带有特定数据源名的前缀。例如,我们使用“part”来表示合并 公司的所有部件数据,不论这些数据驻留在哪里。我们使用“db2_customer”来表示特定 于某个合并前的公司(在本例中,是一个使用 DB2 UDB 作为其 DBMS 的公司)的客户数据 。

查询 1:查找一个感兴趣的 partkey。

select p_name, p_mfgr, p_type, p_partkeyfrom part wherep_type like '%BURNISHED%' andp_name like '%lavender%'order by p_partkeyfetch first 20 rows only;

查询 2:为一个特定的 partkey(例如“28”)搜索在一个给定国家或地区(例如 “GERMANY”)中价格最便宜的供应商

select ps_partkey, s_name, s_suppkey, min(ps_supplycost)from partsupp, supplier, nation whereps_partkey = 28 andps_suppkey = s_suppkey ands_nationkey = n_nationkey andn_name = 'GERMANY'group by ps_partkey,s_name, s_suppkey;

查询 3:奖励那些在一个给定的时间段内发出了大规模订单的客户

select sum(o_totalprice)as totalordered , count(*) as num_orders, c_custkey, c_namefrom customer, orders whereo_custkey = c_custkey andO_ORDERDATE >= DATE('1997-10-01') and O_ORDERDATE < DATE('1998-10-01') group by c_custkey, c_nameorder by totalordered descfetch first 10 rows only;

查询 4:从选定的国家或地区里查找一个部分中的客户,这些客户从另一个部分发出规 模庞大的订单。在本例中,有关这一类型的客户和国家或地区的信息驻留在一个 DB2 DBMS 中,而这些客户通过另一个部分发出的订单信息则驻留在 Oracle DBMS 中。

select c_custkey, c_name, o_totalprice, n_namefrom db2_customer, ora_orders, db2_nation wherec_nationkey = n_nationkey andc_custkey = o_custkey ando_totalprice > 450000 andn_name in ('JAPAN', 'CHINA', 'VIETNAM', 'INDIA');

查询 5:查找某个分公司的符合条件的客户在整个(合并的)公司中所发出的订单的平 均金额。符合条件的客户是指那些居住在给定国家或地区(例如“Japan”)、属于某个给 定的市场段(“HOUSEHOLD”)并有很少欠账的客户。在本例中,有关符合条件的客户和国 家或地区的信息驻留在一个 DB2 DBMS 中。

select avg(o_totalprice) as avg_order, c_name, c_custkey, c_acctbalfrom db2_customer, orders, db2_nation wherec_custkey = o_custkey andc_nationkey = n_nationkey andn_name = 'JAPAN' andc_mktsegment = 'HOUSEHOLD' andc_acctbal >= 0 and c_acctbal <= 1000group by c_custkey, c_name, c_acctbalorder by avg_order desc;

青春一经典当即永不再赎

跨多个数据源的J2EE开发:细节探讨

相关文章:

你感兴趣的文章:

标签云: