 | 级别: 初级 C. M. Saracco, Senior Software Engineer, IBM Jacques Labrie, Senior Software Engineer, IBM Julien Muller, Software Engineering Intern, IBM
2004 年 7 月 01 日 本文将解释为什么使用 Enterprise Information Integration 技术开发那些集成来自不同数据源数据的门户应用程序组件时能够简化设计问题,并可以将编码需求裁减 50% 甚至更多。本文还将考察一个项目,在该项目中,我们分别使用 EII 和不使用 EII 来构建功能相同的门户组件。
门户应用程序开发人员经常碰到的一个设计问题是:构建那些访问和集成来自各种不同数据源数据的软件组件的最好方法是什么?
最容易想到的方法是自己编写数据集成代码,但这种方法会让您花费超乎想象的时间和精力。确实如此,就拿我们的一个项目来说,我们发现,当我们自己管理数据集成工作时,自己编写的代码比起将这一工作委托给一个 Enterprise Information Integration (EII) 软件层来要多出一倍。在本文中,我们将带您看一看我们所做的工作,以便您能够理解每种方法所牵涉到的权衡。
在某些方面,本文可以说是
"用 DB2 Information Integrator 扩展门户" 的第二代。那篇文章研究了开发人员如何使用 EII 扩展现有门户组件(例如 IBM 的 My Query Reports 和 JDBC Business Object Builder)的作用范围和功能。不过那篇文章主要关注在不编写 Java 代码的情况下构建数据集成门户,而本文的重心在于通过编写自己的门户组件来完成类似的工作。在开始之前,我们将告诉您更多关于我们的示例开发项目及其底层环境的情况。
项目概览
为了帮助我们调查 EII 技术如何使门户开发人员受益,我们构建了一个示例门户应用程序,用它来模拟对保险代理的客户服务中心的支持。我们的门户允许使用呼叫中心代表(call center representative)来回答客户的问题,例如判断索赔状态或者复审策略信息。
与大多数门户开发人员一样,我们希望用户可以单点访问完成其工作所需的所有信息。对于我们,这意味着提供对 DB2 UDB 数据库、Lotus Domino 数据库和 XML 文件的访问。DB2 管理客户联系方式和策略信息。Domino 存储索赔单信息,包括索赔性质、索赔日期以及对由谁对错误负责的判断。XML 文件包含事件的警方报告,我们可以通过事件编号将事件追溯到索赔单。图 1 展示了一份经过简化的该数据模式的视图。
图 1. 客户、索赔单和事件数据
我们研究了用来支持直接请求的设计和开发选项:显示与某个给定客户相关的所有索赔单和事件报告。从技术上来讲,这种请求相当简单。对于入门者来说,这涉及到一个等于("=")的搜索谓词(特定的客户 ID)。此外,它没有聚合(aggregation)、日期/时间表达式或者更复杂的数据过滤操作。最后,它不涉及数据集的集成,否则就会产生排序或更复杂的连接(join)逻辑。实际上,这种请求之所以非常简单部分是由于我们可以在数据模式上随心所欲地施加约束,例如索赔单与事件报告之间的 1:1 关系。
您可能想知道为什么我们选择一个这么简单的案例。实际上,之前我们已经研究过在使用 servlet 和实体 Enterprise JavaBeans (EJBs) 实现对异种数据的更复杂查询时所涉及的设计和开发方面的权衡。
"将 DB2 Information Integrator 用于 J2EE 开发:成本/收益分析"和
"针对各种全异的数据源开发实体 EJB 的经验" 中对这些项目已作了描述,它们使用更广范围的数据对象,所有这些对象都可以通过 JDBC 接口来访问。而对于这个项目,我们是想理解使用每种数据源的本地应用程序编程接口(API)集成数据(甚至是以一种简单的方式)的影响。
此外,我们选择手工编写我们的自定义
portlet(或者说门户应用程序组件)来支持我们的工作。一种实现是使用 DB2 Information Integrator (DB2 II) 来提供 EII 服务和管理数据访问工作。另一种实现是直接访问每种数据源来提供等价的功能。最后,基于 DB2 II 的情况下需要大约 180 行代码,而在手工编写的情况下却需要大约 380 行的代码。当我们在本文后面讨论中遇到连接管理、数据检索和数据合并等问题时,您就会开始明白这是为什么。
不过,如果您还不熟悉 EII 技术、联邦数据服务或者 DB2 II,我们建议您先阅读
"用 DB2 Information Integrator 扩展门户"。这篇文章将快速概述 DB2 II,并解释如何让联邦数据管理技术在门户环境中发挥作用。
软件配置
为了支持我们的工作,我们安装了下面一些软件产品:
- WebSphere Portal Version 5.0
- WebSphere Application Server Version 5.0.1
- WebSphere Studio Application Developer Version 5.0.2(该软件仅用于开发和测试)。
- Portal Toolkit Version 5 for WebSphere Studio(该软件仅用于开发和测试)。
- DB2 Information Integrator Version 8.1 (该软件仅用于基于 EII 的 portlet 和对 fixpacks 3 和 fixpacks 5 进行独立测试)。
- DB2 Universal Database Version 8.1 (我们在不同场景中对 fixpacks 3 和 fixpacks 5 进行测试)。
- Lotus Domino 5.0.10
- 支持 XML 1.0 版本文档的 XML 解析器。
这给我们提供了两套运行时配置,它们都显示在图 2 中。
图 2. 运行时体系结构
portlet 体系结构
与所有设计在 WebSphere Portal 中运行的 portlet 一样,我们的 portlet 是以 Java servlet 的一个子类的形式实现的。因此,我们在工作中采纳了 servlet 开发的很多“最佳实践”指南。这包括实现 Model-View-Controller (MVC) 体系结构,在该体系结构中,我们用 JavaBeans 表示我们的数据模型,用 Java Server Pages (JSPs) 提供视图或表示层,我们的 portlet 则用作控制器对象。如果您对 MVC 设计模式不熟悉,有大量的书籍和网站可以提供关于这方面的教程信息。(其中一个资源就是 IBM Redbook
Legacy Modernization with WebSphere Studio Enterprise Developer
的第 4 章。)
按照这个设计,我们的 JavaBeans 包含用来访问每个所需数据源和检索适当数据的逻辑。在使用 DB2 II 时,我们只编写了一个这样的 bean。而在不使用 DB2 II 的情况下,我们为每个数据源都编写了一个 bean (一个用于 DB2 UDB,一个用于 Domino,还有一个用于 XML 文档)。
portlet 代码调用 JavaBean 中的适当方法执行数据检索工作,并确保合并后的结果集可以供 JSP 使用,该 JSP 用于为用户显示数据。在使用 DB2 II 的情况下,只需 portlet 的
doView 方法中的几行代码来建立单数据源连接,并执行单独的查询。而在不使用 DB2 II 的情况下,则需要很多行代码来完成下列工作:
- 构建(并稍后填充)一个散列表来保留最终结果。
- 为每个包装了一个数据源的 JavaBean 调用适当的方法。这些方法连接到或打开必需的数据源对象,并检索所需的数据。
- 调用 JavaBean 来集成结果集。
在随后的小节中我们将逐一介绍 portlet 逻辑中的一些关键方面。
连接管理和数据访问初始化问题
从任何给定的数据源检索数据通常都需要进行一些初始化活动,例如连接到数据源和提供有效的认证信息。例如,多用户关系数据库管理系统(DBMS)要求程序员用有效的用户 ID 和口令连接到数据库。Domino 数据库访问要求程序员初始化 Notes 会话并连接到数据库。在 XML 文件内搜索数据之前,程序员必须先打开和解析该文件。
因为 DB2 II 为远程数据源提供了一个虚拟的数据库映像,所以我们不需要为每个这样的活动编写代码。相反,我们可以使用 DataSource 对象(WebSphere 中定义的一种使用连接池机制的数据库连接)连接到我们的 DB2 II 数据库,这正是我们准备做的。我们使用与 DB2 II DataSource 对象相关的 Java 2 Connector (J2C) 别名来封装用户 ID/口令信息。以下是代码:
清单 1. 使用 DB2 II 情况下的连接管理
// This is in the portlet's doView() method
. . .
// If we haven't already looked up data source, do it now
if(ds==null){
//Retrieve a datasource through the JNDI name service
java.util.Properties parms = new java.util.Properties();
parms.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.ibm.websphere.naming.WsnInitialContextFactory");
try{
//Create the initial name context
javax.naming.Context ctx = new javax.naming.InitialContext(parms);
//Look up the DataSource object
ds = (javax.sql.DataSource) ctx.lookup(dsName);
ctx.close();
. . .
// get a connection to the DataSource
con = ds.getConnection();
} catch(Exception e){
. . .
}
. . .
}
. . .
if(!((Query1)dataBean).connect()){
System.out.println("Not able to connect to the database");
log.error("cotton:JDBCPortlet:Could not connect to the database" +
"Please check user, password and url");
}
. . .
// This is in an abstract class used by our DB2 II JavaBean
/ *
* connects to the datasource:
* 1. directly over jdbc
* 2. uses a connection instance from the connection pool
* @return boolean
*/
. . .
public boolean connect(){
if(isDS == false){
try {
Class.forName(driver).newInstance();
con = DriverManager.getConnection(url,user,pass);
}catch(Exception e){System.out.println("direct JDBC connection error: " + e);
return false;
}
return true;
}
else{
try{
con = ds.getConnection();
return true;
}catch(Exception e){
System.out.println("datasource connection error: " + "check jndi setup");
return false;
}
}
}
|
当我们直接访问每个数据源时,自然就有更多的代码要编写。这包括使用 DataSource 对象连接到 DB2 UDB 数据库、连接到 Domino 数据库(不用 DataSource 对象)和解析相关的 XML 文档。当然,我们不得不使用不同的 Java 类和方法来支持每一个活动,这比起前一种方法来需要更多的编程技巧。并且我们还需要更清楚地知道我们数据的分布式特性。
最后值得注意的是,如果我们需要连接到多个有不同认证需求的数据源,那么对于每个数据源都需要获得(和指定)有效的用户 ID 和口令,我们可能要在应用程序中硬编码多个安全性对象来做这些。在测试场景中,我们可以避免为 Notes 数据库指定用户 ID 和口令,因为我们是将它构建为本地的、不受保护的数据库。然而,这不是通常的生产环境。
下面是我们用来直接连接到 DB2 UDB 数据库的代码:
清单 2a. DB2 UDB 连接
// This is in the portlet's doView() method
. . .
// If we haven't already looked up data source, do it now
if(ds==null){
//Retrieve a datasource through the JNDI name service
java.util.Properties parms = new java.util.Properties();
parms.setProperty(javax.naming.Context.INITIAL_CONTEXT_FACTORY,
"com.ibm.websphere.naming.WsnInitialContextFactory");
try{
//Create the initial name context
javax.naming.Context ctx = new javax.naming.InitialContext(parms);
//Look up the DataSource object
ds = (javax.sql.DataSource) ctx.lookup(dsName);
ctx.close();
. . .
// get a connection to the DataSource
con = ds.getConnection();
} catch(Exception e){
. . .
}
. . .
}
. . .
if(!db2.connect()){
System.out.println("Not able to connect to the datasource: DB2");
log.error("cotton:JDBCPortlet:Could not connect to the database" +
"Please check user, password and url");
}
// This is in an abstract class used by our DB2 UDB JavaBean
/ *
* connects to the datasource:
* 1. directly over jdbc
* 2. uses a connection instance from the connection pool
* @return boolean
*/
. . .
public boolean connect(){
if(isDS == false){
try {
Class.forName(driver).newInstance();
con = DriverManager.getConnection(url,user,pass);
}catch(Exception e){System.out.println("direct JDBC connection error: " + e);
return false;
}
return true;
}
else{
try{
con = ds.getConnection();
return true;
}catch(Exception e){
System.out.println("datasource connection error: " + "check jndi setup");
return false;
}
}
}
|
下面是我们用来连接到 Domino 服务器以及请求访问 Lotus Notes 数据库的代码:
清单 2b. Domino 连接
// This is in the portlet's doView() method
/* The call to the Notes constructor specifies the database of interest
* Actual connection to this database will be done as part of the
* access() method in the JavaBean
*/
. . .
// getRealPath() provides the fully qualified name of Notes file.
// In our test scenario, this file was stored within our portal project
// because we wanted to ship the application and most of its data as
// a single unit. In a production environment, access to the
// Notes database would probably need to be specified in a different manner.
NotesAccess notes = new NotesAccess
(getPortletConfig().getContext().getRealPath("Data/Claims2.nsf"));
notes.access(CUSTOMER_ID,incidents);
. . .
// This is part of the access() method in the Domino Notes JavaBean
. . .
try{
// Initialize a notes session
NotesThread.sinitThread();
}
catch (Exception e){ // cannot create Domino thread
. . .
}
try{
Session s = NotesFactory.createSession();
// Connect to the database
Database claims = s.getDatabase(null,notesurl);
// Test if we could open the database
if (!claims.isOpen()){
System.out.println("Not able to connect to the server notes database");
. . .
}
. . .
catch (Exception e) {
. . .
}
|
由于 XML 文档是本地引用的文件,因此不需要任何连接代码。不过,portlet 必须指定相关的 XML 文件,并且调用 JavaBean 上的方法为随后在 XML 文件内对内容的搜索作准备。
清单 2c. XML 文件准备
// This is in the portlet's doView() method
/* The call to the XML JavaBean constructor specifies the file of interest
* The JavaBean's access() method includes code to prepare for searching the file
*/
. . .
// getRealPath() provides the fully qualified name of the XML file.
// In our test scenario, this file was stored within our portal project
// because we wanted to ship the application and most of its data as
// a single unit.
XMLAccess xml = new XMLAccess
(getPortletConfig().getContext().getRealPath("Data/PoliceReports.xml"));
xml.access(incidents);
. . .
// This is in the XML JavaBean's access() method
// Shown below is only the code to prepare for search of the file
. . .
// Create a DOM parser
DOMParser parser= new DOMParser();
try{
// Parse the file and get it as a tree
parser.parse(fileURL);
Document doc = parser.getDocument();
// Establish elements of the tree
Node reports = doc.getDocumentElement();
NodeList reportList = reports.getChildNodes();
NodeList reportElemList;
. . .
}
catch (Exception e) {
. . .
}
|

 |

|
数据检索问题
在准备工作完成之后(主要是连接管理),我们必须检索数据。记得我们的目标相当简单 -- 对于给定的客户,找出他/她的所有索赔单以及与他们有关的任何警方报告。当然,我们必须以适当的数据访问代码来表达这种业务请求。
在使用 DB2 II 的情况下编写这种代码时,要做的工作很少。我们只需简单地发出一条 SQL 语句并处理结果即可。DB2 II 负责把该查询分解成能被每个目标数据源处理的几块。DB2 II 还以一种对我们透明的方式代表我们确定和执行适当的数据访问策略。
我们只是在 portlet 的
doView 方法中编写了几行代码,以调用一个方法来执行查询:
清单 3. 使用 DB2 II 情况下 portlet 的数据访问
//try to execute the SQL query
if(!((Query1)dataBean).executeQuery()){
System.out.println("Not able to execute the query");
log.error("cotton:SQLPortlet: Could not execute the sql query");
}
|
executeQuery 方法中的代码验证数据库连接是否存在,并执行查询。下面是数据访问代码:
清单 4. 使用 DB2 II 情况下的 JavaBeans 和相关代码
// This code is in our DB2 II JavaBean
. . .
public boolean executeQuery( . . .) {
. . .
if(!cust_id.equals(""))
// ***********************************************************************************************
// Execute the following query to get all reports, claims and customer info based on policy#
// ***********************************************************************************************
return super.executeQuery("
SELECT
DEMO.CUSTOMER_DB2.CUSTOMER_ID, DEMO.CUSTOMER_DB2.FIRST_NAME || ' ' ||
DEMO.CUSTOMER_DB2.LAST_NAME, DEMO.CUSTOMER_DB2.ADDRESS, DEMO.CUSTOMER_DB2.CITY,
DEMO.CUSTOMER_DB2.STATE, DEMO.CUSTOMER_DB2.POLICY_TYPE, DEMO.CUSTOMER_DB2.POLICY_PREMIUM,
DEMO.CLAIMS_DOMINO.ODATE, DEMO.CLAIMS_DOMINO.CDATE, DEMO.CLAIMS_DOMINO.INCIDENT_ID,
DEMO.CLAIMS_DOMINO.DOC_ID,
DEMO.REPORTS_XML.OFFICERNAME, DEMO.REPORTS_XML.INCIDENTDESC
FROM
DEMO.CLAIMS_DOMINO, DEMO.REPORTS_XML,DEMO.CUSTOMER_DB2
WHERE
DEMO.CLAIMS_DOMINO.INCIDENT_ID = DEMO.REPORTS_XML.INCIDENTID AND
DEMO.CUSTOMER_DB2.CUSTOMER_ID = INTEGER(DEMO.CLAIMS_DOMINO.CUSTOMER_ID) AND
(DEMO.CLAIMS_DOMINO.INCIDENT_TYPE = 'Fault' OR DEMO.CLAIMS_DOMINO.INCIDENT_TYPE = 'No Fault') AND
DEMO.CUSTOMER_DB2.CUSTOMER_ID=" + cust_id
);
else return false;
. . .
public String getCustID(){
try{
return rs_c.getString(1);
}catch(SQLException e){ . . . }
}
public String getName(){
try{
return rs_c.getString(2);
}catch(SQLException e){. . . }
}
. . .
}
// This code is included in another class (an abstract class used by our JavaBean).
// After a SQL query string is defined, this method is called to execute it.
protected boolean executeQuery(String sql){
try{
stmt_c = con.prepareStatement(sql,ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);
rs_c = stmt_c.executeQuery();
}
catch(SQLException e){
. . .
}
. . .
}
|
在不使用 DB2 II 的情况下,我们需要使用本地 API 从每个数据源逐个检索数据。我们还需要确定如何将一个逻辑查询拆散成针对每个数据源的单独数据检索活动。最后,我们还需要确定整体数据访问策略 -- 先检索什么数据,如何在每个源上过滤数据等。
最终证明后两项相对比较简单,因为我们的查询是如此简单。不过,过去的经验表明,事情并非总是如此。实际上,当我们
比较 servlet 所做的工作 时,我们发现分解更复杂的查询和开发高效的数据访问策略需要大量的时间、精力和专门技术。实际上,为了在提供合理性能的同时保留初始的查询语义,我们需要反复地设计、编码和测试。在本文的前面部分我们提到过,我们有意地避免在我们的 portlet 工作中再次遇到这样的问题,因此我们将关注的焦点限定在一个非常简单的业务任务上。
portlet 的
doView 方法调用来自多个 JavaBean 的方法,用它来启动每个数据源上的数据检索活动。首先,它从 DB2 UDB 数据库检索适当的客户信息。接着,它从 Domino 检索目标客户的索赔单信息。最后,它从一个 XML 文件中检索适当索赔单的事件报告(警方报告)。之后,所有这些数据被组合起来,准备好,以便由 JSP 将它们最终呈现出来。
清单 5. 不使用 DB2 II 情况下的 portlet 数据访问代码
//Execute the query without ii:
// Create an Hashtable to store the results
Hashtable incidents = new Hashtable();
// Connect to DB2 UDB, execute the query and get the result
// using the DB2 access class
. . .
db2.setCust_id(((Query1)dataBean).getCust_id());
//try to execute the queries against each data source
if(!db2.executeQuery()){
System.out.println("Not able to execute the query");
log.error("cotton:SQLPortlet: Could not execute the sql query");
}
db2.getNextResult();
String CUSTOMER_ID = db2.getCustID();
String CUSTOMER_NAME = db2.getName();
String ADDRESS = db2.getAddr();
String CITY = db2.getCity();
String STATE = db2.getState();
String POLICY_TYPE = db2.getType();
String POLICY_PREMIUM = db2.getCost();
// Get the results from Notes and store all the matching
// incidents from notes into the hashtable
NotesAccess notes = new NotesAccess
(getPortletConfig().getContext().getRealPath("Data/Claims2.nsf"));
notes.access(CUSTOMER_ID,incidents);
// Include the XML data in the hashtable
XMLAccess xml = new XMLAccess
(getPortletConfig().getContext().getRealPath("Data/PoliceReports.xml"));
xml.access(incidents);
// Put the result in a bean to pass it to the jsp page ...
JoinBean join = new JoinBean
(incidents.values(),CUSTOMER_ID,CUSTOMER_NAME,ADDRESS,CITY,STATE,POLICY_TYPE,POLICY_PREMIUM);
s.setAttribute(dataBeanName,join);
. . .
|
portlet 的
doView 方法中的代码依赖于三个 JavaBean 类来处理实际的数据检索。以下是来自 DB2 UDB JavaBean 的摘录:
清单 6a. 用于 DB2 UDB 访问的 JavaBean
// This code is in the DB2 UDB JavaBean
. . .
private String sql = "
SELECT DEMO.CUSTOMER_DB2.CUSTOMER_ID,DEMO.CUSTOMER_DB2.FIRST_NAME ||
' ' || DEMO.CUSTOMER_DB2.LAST_NAME, DEMO.CUSTOMER_DB2.ADDRESS,
DEMO.CUSTOMER_DB2.CITY, DEMO.CUSTOMER_DB2.STATE, DEMO.CUSTOMER_DB2.POLICY_TYPE,
DEMO.CUSTOMER_DB2.POLICY_PREMIUM
FROM
DEMO.CUSTOMER_DB2
WHERE DEMO.CUSTOMER_DB2.CUSTOMER_ID =";
. . .
/**
* executes the query
* @return boolean
*/
public boolean executeQuery(){
return super.executeQuery(sql + cust_id);
}
public String getCustID(){
try{
return rs_c.getString(1);
}catch(SQLException e){return "";}
}
public String getName(){
try{
return rs_c.getString(2);
}catch(SQLException e){return "";}
}
. . .
// This code is included in another class (an abstract class used by our JavaBean).
// After a SQL query string is defined, this method is called to execute it.
protected boolean executeQuery(String sql){
try{
stmt_c = con.prepareStatement(sql,ResultSet.TYPE_SCROLL_SENSITIVE,ResultSet.CONCUR_READ_ONLY);
rs_c = stmt_c.executeQuery();
}
catch(SQLException e){
. . .
}
. . .
}
|
从 DB2 中检索与给定客户相关的数据之后,我们使用该客户的 ID 从 Domino 数据库中搜索由该客户归档的索赔单。索赔单可能引用警方报告。我们采用三个助手类(Incident)来协助我们处理事件数据。为简便起见,我们省略了那个类的代码。
DB2 II 的一个关键特性是对应用程序开发人员隐藏数据的实际存储的能力。这样以来,应用程序开发人员就无需知道数据存储在哪里。然而,正如我们的代码所演示的,在不使用 DB2 II 的情况下,数据的位置对开发人员不是透明的。
清单 6b. JavaBean 用于 Domino 访问的 JavaBean
// establish a connection to the database
. . .
Item it;
DateTime dt=null;
// Get a view (List of the documents)
View v = claims.getView("By Policy");
// Take all the documents one by one to check the Customer_ID
Document doc = v.getFirstDocument();
while(doc!=null){
if(doc.isValid()){ // if: level 1
// Check if the doc has a matching cust id,
// get the values needed if it is the case
if(doc.hasItem("Customer_ID")){ // if: level 2
if(doc.getFirstItem("Customer_ID").getValueString().equals(cust_id)){ // if: level 3
Incident incident = new Incident(doc.getFirstItem("Incident_ID")getValueString());
if(doc.hasItem("ODate")){
it = doc.getFirstItem("ODate");
if(it!=null) { dt = it.getDateTimeValue(); }
if(dt instanceof DateTime) { incident.setODate(dt.toString()); }
}
if(doc.hasItem("CDate")){
it=doc.getFirstItem("CDate");
if(it!=null){
dt = it.getDateTimeValue();
if(dt instanceof DateTime) incident.setCDate(dt.toString());
}
}
incident.setDoc_id("");
incidents.put(incident.getIncident_id(),incident);
} // end if level 3
} // end if level 2
} // end if level 1
doc = v.getNextDocument(doc);
} // end while loop
. . .
|
通过处理 Domino 数据可以得到相关的事件 ID。有了这个信息,接着我们就可以访问基于 XML 的警方报告,检索所需的详细信息(例如将该报告归档的官员的姓名以及对事件的描述)。
如前所述,DB2 II 的一个关键特性是对应用程序开发人员隐藏数据的实际存储的能力。XML 文件的名称是在配置 DB2 II 来访问 XML 时指定的。因此,应用程序开发人员无需知道数据存储在哪里。这与对 Lotus Domino 数据库的访问是一样的。应用程序开发人员无需知道数据库的物理位置,因为 DB2 II 会管理这些信息。但是,正如我们的代码所演示的那样,在不使用 DB2 II 的情况下,数据的位置对开发人员来说不是透明的。
以下是来自 XML 访问 bean 的摘要,这段摘要展示了对文件进行搜索的逻辑:
清单 6c. 用于 XML 访问的 JavaBean
. . .
// Walk the DOM tree to search for data of interest
for (int i=0;i<reportList.getLength();i++){
Node report = reportList.item(i);
reportElemList = report.getChildNodes();
// Go through each report from the tree
for (int j=0;j<reportElemList.getLength();j++){
Node elem = reportElemList.item(j);
// If we have an incidentID TAG
if(elem.getNodeName().equals("IncidentID")){
// Check the hashtable to see if we have to add it
if(ht.containsKey(elem.getFirstChild().getNodeValue())){
Incident inc = (Incident) ht.get(elem.getFirstChild().getNodeValue());
// Copy the data
for (int k=0;k<reportElemList.getLength();k++){
Node elem2 = reportElemList.item(k);
if(elem2.getNodeName().equals("OfficerName"))
inc.setOfficername(elem2.getFirstChild().getNodeValue());
if(elem2.getNodeName().equals("IncidentDesc"))
inc.setIncidentdesc(elem2.getFirstChild().getNodeValue());
} // end for loop
} // end inner if
} // end outer if
} // end inner for loop
} // end outer for loop
|
数据合并问题
与数据有关的工作的最后一步是为表示层提供合并了的信息集,以便将它们显示出来。在使用 DB2 II 的情况下,我们不需要检索和返回合并的结果集。
而在不使用 DB2 II 的情况下,就有很多工作要做。同样,由于我们初始查询很简单,所以这工作也非常简单。我们不必实现复杂的连接(join)处理逻辑,不必执行额外的排序,不必消除重复数据,也不必执行集合操作。这些活动会大大增加工作的复杂度。相反,我们只编写了一个助手类,该助手类只从不同数据源中提取检索到的数据值,并将这些数据值放到一个单独的、经过合并的结构中。我们还提供了这个类的一些属性的存取方法,但是为了简便起见,在本文中我们省略了这些方法。
清单 7. 用于合并不同数据的助手类
. . .
**
* @author IBM
*
* This bean contains the data stored as the same format
* as Query1.class. This bean is used in the native case.
* It gives the capacity to send this bean instead of
* the other one to the jsp page
*/
public class JoinBean implements ShowCustBean{
String CUSTOMER_ID="";
String CUSTOMER_NAME="";
String ADDRESS="";
String CITY="";
String STATE="";
String POLICY_TYPE="";
String POLICY_PREMIUM="";
Iterator i;
Incident inc = null;
Collection c = null;
}
/* Method JoinBean.constructor to instantiate a bean instance,
* in which all necessary customer information is stored.
* @param c: hashtable, which contains Incident instances.
* For each incident of the customer, one incident instance is stored in the hashtable
*/
public JoinBean(Collection c,String CUSTOMER_ID,
String CUSTOMER_NAME,String ADDRESS,
String CITY,String STATE,String POLICY_TYPE,
String POLICY_PREMIUM){
this.CUSTOMER_ID = CUSTOMER_ID;
this.CUSTOMER_NAME = CUSTOMER_NAME;
this.ADDRESS = ADDRESS;
this.CITY = CITY;
this.STATE = STATE;
this.POLICY_TYPE = POLICY_TYPE;
this.POLICY_PREMIUM = POLICY_PREMIUM;
this.c = c;
this.i = c.iterator();
}
. . .
|

 |

|
结束语
开发需要集成异种数据并为用户提供惟一的、经过合并的结果的自定义 portlet,这是一项具有挑战性的任务。在本文中,我们比较了完成这项工作的两种方法,我们发现使用 DB2 II 中的 EII 技术可以大大节省精力,减少了 50% 多需要编写的代码,而且还可以将完成我们工作所需的技能要求降到最低。即使我们的数据集成工作极其简单,该方法也可以做到这一点。如果我们需要访问三个以上的数据源,或者需要实现涉及连接、聚合以及其他高级功能的复杂查询,那么使用 DB2 II 就可以减少更多的开发。
值得注意的是,我们比较了只实现一个查询的编码需求。在前面的文章中,我们研究了涉及不同数据源的多个查询,发现基于 EII 的架构使我们在扩展应用程序组件来包含更多查询时,能够重用很多已有的编码基础结构。这是因为 DB2 II 使我们能够使用对公用 API 进行编码,处理公用数据结构(合并的结果集),以及避免对硬编码处理连接函数、聚集函数和其他函数的逻辑的需要。在不使用 DB2 II 的情况下,不同的工作量要求我们对代码的基础结构进行更大范围的改造。如果这些工作量涉及不同的数据源,那么随着更多 API、更多数据结构、更多数据类型转换和更多语义方面的不同的引入,我们面临的挑战将进一步被加剧。
参考资料
- 您可以参阅本文在 developerWorks 全球站点上的
英文原文.
-
Alur、Nagraj 和 Isaac Allotey-Pappoe、Chris Delgado、Jayanthi Krishnamurthy。
WebSphere Portal and DB2 Information Integrator,IBM Redbook review draft,2003 年 12 月,IBM publication number SG24-6433-00。请访问
IBM Redbooks 站点。
-
Bruni、Paulo 和 Francis Arnaudies、Amanda Bennet、Susanne Englert、Gerhard Keplinger。
Data Federated with IBM DB2 Information Integrator V8.1,IBM Redbook,2003 年 10 月,IBM publication number SG24-7052-00。请访问
IBM Redbooks 站点。
-
Credle、Rufus 和 Faheem Altaf、Serena Chan、Fernanda Gomes、Sunil Hiranniah、Pralhad Khatri、Shun Zhong Li、Vikrant Mastoli。
IBM WebSphere Portal for Multiplatforms V5 Handbook,IBM Redbook review draft,2004 年 1 月,IBM publication number SG24-6098-00. 请访问
IBM Redbooks 站点。
-
Labrie、Jacques 和 Mary Roth。
"利用 DB2 Information Integrator 简化门户应用程序,"
IBM developerWorks 教程,2004 年 1 月。
-
Saracco, C. M. "用 DB2 Information Integrator 扩展门户",
IBM developerWorks 文章,2004 年 2 月。
-
Saracco, C. M 和 T. F. Rieger。
"Accessing Federated Databases from Application Server Components",
IBM developerWorks 文章,2003 年 2 月。
-
Saracco,C. M.
"在 Web 应用程序中使用全异数据",
IBM developerWorks 文章,2002 年 8 月。
-
Saracco、C. M. 和 Susanne Englert,Ingmar Gebert。
"将 DB2 Information Integrator 用于 J2EE 开发:成本/收益分析" ,
IBM developerWorks 文章,2003 年 5 月。
-
Wahli、Uehli 和 Masaaki Agatsuma、Reginaldo Barosa、Gert Hekkenberg、Bob McGoogan、 Iwan Winoto。
Legacy Modernization with WebSphere Studio Enterprise Developer,IBM Redbook, 2002 年 12 月,IBM publication number SG24-6806-00。请访问
IBM Redbooks 站点。
作者简介  | |  |
Cynthia M. Saracco 是 IBM 硅谷实验室的资深软件工程师。她曾编写过两本关于数据库管理方面的书(其中有一本是与 Charles J. Bontempo 合著),并教过 Java 开发和数据库管理问题方面的课程。
|
 | |  |
Jacques Labrie 是 IBM 硅谷实验室的资深软件工程师。最近 15 年的时间他都在开发和管理数据管理产品,目前将主要精力放在 DB2 II 与 WebSphere 产品家族的集成上。
|
 | |  |
Julien Muller 是 IBM 硅谷实验室的的一名软件工程实习生。他最近刚从法国的 Paris Dauphine 大学毕业,并获得计算机科学硕士学位,他专门研究数据库。
|
对本文的评价
|  |