实现 IBM InfoSphere Master Data Management Server 的自定义查询事务

定制和扩展 MDM Server

了解如何使用 MDM Server WorkbenchLearn 实现新的查询事务,以扩展 IBM ® InfoSphere® Master Data Management Server。

Catherine Griffin, 软件工程师, IBM

Catherine Griffin是IBM Hursley, U.K.的一名软件工程师。她是IBM模型转换框架的设计者和主要开发者,现在为IBM Software Services for Websphere组织工作。



2012 年 2 月 06 日

简介

IBM InfoSphere Master Data Management Server (MDM Server) 为客户、帐户与产品主数据提供一个复杂的数据模型,并提供大量服务(称为事务)用于查询与修改主数据记录。它通过支持各种扩展机制允许自定义数据模型与事务行为,或者在需要时完全增加新的事务。

MDM Server 包括 MDM Server Workbench,此工具支持开发 MDM Server 的扩展。这个工作台允许您定义所需的数据模型和事务,并生成实现 MDM Server 扩展所需的代码。对于简单的数据模型扩展,生成的代码可能无需另外处理就能满足您的要求。但如果需要新的事务,您将需要自定义生成代码来实现事务行为。

实现事务需要具备 MDM Server API 与框架的知识,以及通用的 Java™ 技术、J2EE 与 SQL 编程经验。

学习设计新的 MDM Server 事务需要的基本概念,并将重点放在实现新查询上。其中针对构建三个示例事务给出了逐步指导,举例说明了实现自定义查询的简单方法。

目标

在此教程中,您将学会:

  • 各种事务接口风格
  • 各种事务实现风格
  • 如何使用 MDM Workbench 定义新的查询事务
  • 如何以业务代理的形式实现查询事务
  • 一种借助静态 SQL 在自定义实体上实现简单查询的技术
  • 如何借助动态 SQL 在自定义实体上实现简单的搜索事务

先决条件

您需要熟悉 MDM Server 与 MDM Server Workbench 工具,知道如何使用工具台开发简单的数据模型扩展,并拥有使用 Eclipse 开发环境进行 Java 编程的经验。

系统要求

为了满足文中要求,您需要一个 MDM Server 开发环境。本教程是使用安装在 Rational® Software Architect V7.5.5 上的 MDM Server Workbench V9.0.2,以及 WebSphere® Application Server V6.1 与 DB2® 进行开发的。

如果您使用的是 MDM Server 的早期版本,相同的概念与技术也同样适用,但您可能无法使用这里描述的某些工作台功能。


实现自定义的查询事务

MDM Server 提供几百种用于查询与修改主数据的事务。如果您对现有事务没有提供的查询有业务需求,而且通过使用 MDM Server 支持的机制自定义现有事务不可能满足要求,那么您可以实现一种新的查询事务。

这可能是因为您已经将 MDM Server 数据模型扩展为支持新类型的记录,或者因为您需要查询所有现有事务均不支持的记录组合。

实现一个新的查询事务的步骤如下:

  1. 了解需求
  2. 设计事务接口
  3. 决定如何实现事务
  4. 使用 MDM Server Workbench 为事务生成骨架代码
  5. 自定义生成的代码以满足需求
  6. 部署与测试新事务

选择事务接口风格

MDM Server 支持两种风格的事务接口:Inquiry 与 Txn。此选择将影响事务请求消息的格式和实现事务的方式。

Inquiry 风格的事务接受若干字符串参数,被定义为一组固定的命名参数。

这类事务的一个示例是 getPerson。要使用基于 RMI 消息的 XML 来调用 getPerson,该请求 XML 如下:

清单 1. getPerson RMI XML 请求
<TCRMService  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \
xsi:noNamespaceSchemaLocation="myTCRM.xsd">
    <RequestControl>
        <requestID>100181</requestID>
        <DWLControl>
            <requesterName>cusadmin</requesterName>
            <requesterLanguage>100</requesterLanguage>
        </DWLControl>
    </RequestControl>
    <TCRMInquiry>
        <InquiryType>getPerson</InquiryType>
        <InquiryParam>
        	<tcrmParam name="partyId">871122346130007978</tcrmParam>
        	<tcrmParam name="InquiryLevel">0</tcrmParam>
        </InquiryParam>
    </TCRMInquiry>
</TCRMService>

参数包括 partyId(要返回的客户的主键)和 InquiryLevel(指示要返回的细节数量)。

要使用相同的值通过 Web 服务接口调用 getPerson,该请求消息如清单 2 所示(省略 SOAP 封装)。

清单 2. getPerson Web 服务 XML 请求
<q0:GetPerson>
  <control>
    <requestId>100181</requestId>
    <requesterName>cusadmin</requesterName>
    <requesterLanguage>100</requesterLanguage>
  </control>
  <partyId>871122346130007978</partyId>
  <inquiryLevel>0</inquiryLevel>
</q0:GetPerson>

Inquiry 风格仅适用于带有少量参数的查询,如果使用这种风格,您不可能频繁修改接口。Txn 风格通常被认为是新事务的最佳实践。Txn 风格的事务接受一个用于定义事务参数的请求对象。该请求对象的字段多少可以根据需要确定,包括嵌套的子对象。

这类事务的一个示例是 searchPerson。要使用基于 RMI 消息的 XML 来调用 searchPerson,该请求 XML 如清单 3 所示。

清单 3. searchPerson RMI XML 请求
<TCRMService xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" \
xsi:noNamespaceSchemaLocation="myTCRM.xsd">
    <RequestControl>
        <requestID>92345</requestID>
        <DWLControl>
            <requesterName>cusadmin</requesterName>
            <requesterLanguage>100</requesterLanguage>
        </DWLControl>
    </RequestControl>
    <TCRMTx>
        <TCRMTxType>searchPerson</TCRMTxType>
        <TCRMTxObject>TCRMPersonSearchBObj</TCRMTxObject>
        <TCRMObject>
            <TCRMPersonSearchBObj>
                <GivenNameOne>Jane</GivenNameOne>
                <LastName>Smith</LastName>
            </TCRMPersonSearchBObj>
        </TCRMObject>
    </TCRMTx>
</TCRMService>

要使用相同的值通过 Web 服务接口调用 searchPerson,该请求消息如清单 4 所示(省略 SOAP 封装)。

清单 4. searchPerson Web 服务 XML 请求
<q0:SearchPerson>
  <control>
    <requestId>92345</requestId>
    <requesterName>cusadmin</requesterName>
    <requesterLanguage>100</requesterLanguage>
  </control>
  <personSearch>
    <givenNameOne>Jane</givenNameOne>
    <lastName>Smith</lastName>
  </personSearch>
</q0:SearchPerson>

Inquiry 风格的接口只用于查询事务,而 Txn 风格的接口始终用于增加与修改主数据记录的事务,也可以用于查询。

决定如何实现事务

MDM Server 支持两种实现事务的方式:Business Proxy 类或 Component 方法。您的选择不依赖于接口风格。

为了理解其中的差异,需要简要介绍 MDM Server 事务框架的架构。该框架分为三层,每层都在实现事务的过程中发挥不同的作用。

图 1. 事务处理层
屏幕截屏显示三个层:Business Proxy、Controller、Component

MDM Server 事务如 addPerson 是由 Controller 类实现,它会依次调用一个或多个 Component 类。当您调用 addPerson 时,默认使用 MDM Server Business Proxy 基类,它会将事务执行委托给 Party Controller 类。通过提供自定义的 Business Proxy 类并将其与 addPerson 相关联,可以自定义 addPerson 事务的行为。

为了实现自定义的事务,您可以提供一个新的 Business Proxy 类或新的 Controller 与 Component 类(您绝不要同时做这两件事情)。

Business Proxy 类可以包含您需要的任意 Java 代码,同时在 Controller 层次上调用其他事务。它绝不会直接调用 Component 类。

Component 与 Controller 类拥有对应于事务的方法,通常 Controller 方法只会调用一个等价的 Component 方法。对于以这种方式实现的自定义事务,您应该忽略 Controller 类,而且不要给它添加自定义代码,这样 Controller 与 Component 方法的行为就会完全一致。注意,不能对 MDM Server 事务这样假设,您将从 addPerson 事务的 Component 级 addPerson 方法中看到不同的行为。

Component 方法可以包含您需要的任意 Java 代码,同时调用其他 Component 方法。

实现新事务最常用的方式是 Business Proxy。Business Proxy 用于实现主要需要调用现有的 MDM Server 事务的新事物,或许还可以修改请求或响应。Business Proxy 在一个 Java 类中实现。

Business Proxy 最适合实现新查询的场景示例是,您可以通过调用现有事务并将结果组合为单一响应来获得所需要的功能。

例如,您可能想使用事务 getPersongetPartyDemographicsByType 查找一条客户记录,同时返回相关的客户统计数据。

Component 方法实现风格实际上意味着提供 Controller 与 Component 类,但事务行为的相关代码位于 Component 类的一个方法中。当事务实现与您的数据模型扩展紧密相关或者您希望从行为扩展或其他 Component 事务调用新事务时,使用该风格是恰当的。Component 级别上的实现允许您使用 MDM Server 框架来实现查询,但这种风格需要更多 Java 类,工作台将为您生成所有骨架代码。接下来,您需要编辑生成的类以添加新查询的逻辑。在某些情况下,以上任一种方法均可使用,但要选择最适合或最容易实现的一种。下面的内容中将给出这两种方法的例子。


使用 MDM Server Workbench 生成骨架代码

在这里,我们将使用 MDM Workbench 逐步构建一组示例查询事务,然后在后续内容中进一步进行开发。为此,您需要建立一个开发环境来扩展 MDM Server:

  1. 第一步是创建一个 Hub Module 项目,以便包含扩展。从工作台菜单中选择 File > New > Project … > Hub Module Project
  2. 输入项目名称与用于生成代码的基本 Java 包名称。一个用于生成的 Web 服务命名空间 URI 将自动为您填入,但您可能想要对其进行修改。
  3. 如果这是您创建的第一个模块项目,那么您必须输入一个 Hub 基名(这用于生成 XSD 与属性文件)和数据库架构名称。
  4. 点击 Finish 即可使用这些设置创建新项目。项目创建完毕后,将打开模块模型进行编辑。
  5. 点击 Model 选项卡开始定义扩展。
  6. 为这个示例定义一个名为 XVehicle 的新实体,用于保存客户拥有车辆的相关信息。除了标准的主键字段,此实体中的字段还包括:
    • licenseNumber:保存这辆车辆的车牌号码的字符串。
    • vehicleType:标识车辆型号的型号编码。
    • owningParty:对拥有这辆车辆的客户的引用。
  7. 为了定义 vehicleType 型号编码,您还需要定义一个新的代码表 XVehicleType

至此,模型应该如下所示:

图 2. 数据模型示例
屏幕截图显示资源管理器的车辆信息视图

工作台支持五类事务编码模式:

  1. Get Record
  2. Add Record
  3. Update Record
  4. Inquiry
  5. Txn

如果定义一个新实体,那么会定义其默认的 get、add 和 update 事务。Add Record 与 Update Record 事务风格只适用于默认的 add 和 update 事务,通过其他任何方式都无法创建。

定义一个自定义的 Get Record 事务

默认的 get 事务(用于查询实体的主键)是一个 Get Record 事务。您可以为新实体创建其他 Get Record 事务,但您无法为开箱即用的 MDM Server 实体创建这些事务。

Get Record 事务是 Inquiry 风格的、Component 实现的查询事务,通过使用一条固定的 SQL 语句查询该实体的一个或多个字段来选择实体记录。对于这种风格的事务,工作台将为基本的工作实现生成所有代码,您可以轻松地自定义生成的 SQL,从而对查询进行调整。

Get Record 风格十分适用于新实体上的简单查询事务。对于像 Person 这类开箱即用的 MDM Server 实体的查询与搜索,或者需要更为复杂或动态的 SQL,您应该创建一个 Inquiry 或 Txn 风格的事务。

为 XVehicle 定义一个新的 Get Record 事务,以根据车牌号码字段查询车辆。

右击 XVehicle,然后从菜单中选择 New > Get Record。如下进行设置。

图 3. getVehiclesByLicense
屏幕截图显示 getVehiclesByLicense;选中的 Multiple records returned,Query parameters 为 licenseNumber

定义一个自定义的 Inquiry 事务

可以将 Inquiry 与 Txn 事务添加到新的持久性实体、事务数据对象或文件夹。它们在模型中所处的位置对行为没有任何影响。您应该只在符合逻辑而且方便时定义它们。

正如它们的名称所暗示的那样,Inquiry 与 Txn 事务之间的区别是接口风格。

接下来,定义一个新的 Inquiry 事务,以返回客户以及拥有车辆的详细信息。为此,您首先需要定义一个新的临时性数据对象用于包含响应数据,这些数据包括一条客户记录和多条车辆记录。创建临时性数据对象的步骤如下:

  1. 右击 VehicleInfo 文件夹,然后选择 New > Transient Data Object
  2. 将新对象命名为 PartyAndVehicles
  3. 右击 PartyAndVehicles 对象,从菜单中选择 New > Containment
  4. 将容器名称设置为 Party,然后选择 Party 为被包含的实体。
  5. 添加另一个 Containment 到 PartyAndVehicles。
  6. 将这个容器名称设置为 vehicles,被包含的实体为 XVehicle,并选择 Many 标志。

模型现在应该如下所示:

图 4. PartyAndVehicles
资源管理器的左侧视图显示所选中的车辆;右侧为所选中车辆的属性

现在可以定义 Inquiry 事务:

  1. 右击 VehicleInfo 文件夹,然后选择 New > Inquiry Transaction
  2. 将事务命名为 getPartyAndVehicles
  3. 将响应类型设置为 PartyAndVehicles。
  4. 将实现设置为 Business Proxy
  5. 右击 getPartyAndVehicles,然后从菜单中选择 New > Parameter
  6. 将参数名称设置为 partyId,类型设置为 Long。

模型现在如图 5 所示。

图 5. getPartyAndVehicles
屏幕截图显示选中的 getPartyAndVehicles

一个 Inquiry 可以有任意数量的参数(但如果参数数量过多,可能引起运行时问题。如果您需要传递的参数很多,我推荐您使用 Txn 风格),并将参数定义给事务。您可以在模型中指定参数类型,但是它们会作为字符串传递给事务。

getPartyAndVehicles 事务将使用 partyId 参数首先获取拥有该主键的 Party 记录,然后再获取与顾客相关的车辆记录。为了简化此项工作,定义另一个 Get Record 事务 (getVehiclesByParty),它将按照 partyId 查询车辆记录。

定义一个自定义的 Txn 事务

接下来为车辆记录定义一个搜索事务。这将是一个 Txn 风格的事务,而请求对象将是一个临时性数据对象。

要搜索的字段是拥有客户姓名、拥有客户类型(Person 还是 Organization)、车牌号码与车辆型号代码。

创建临时性数据对象的步骤如下:

  1. 右击 VehicleInfo 文件夹,然后选择 New > Transient Data Object
  2. 将新对象命名为 XVehicleSearchRequest
  3. 给对象添加字符串类型的属性 partyName
  4. 给对象添加字符串类型的属性 partyType
  5. 给对象添加字符串类型的属性 licenseNumber
  6. 给对象添加型号代码 vehicleType,其值引用 XVehicleType 代码表。

为了节省输入工作量,可以从 XVehicle 复制字段 licenseNumber 和 vehicleType,然后将其粘贴到新对象。

模型现在如图 6 所示。

图 6. XVehicleSearchRequest
图象显示资源管理器中选中的 XVehicldSearchRequest

现在定义搜索事务:

  1. 右击 XVehicle 实体,然后选择 New > Txn Transaction
  2. 将新事务命名为 searchVehicle
  3. Action Category 设置为 View,表明这是一个查询。(Txn 事务也用于添加与更新。)
  4. 将请求类型设置为 XVehicleSearchRequest 对象。
  5. 应该选中 Multiple records returned。
  6. 将响应类型设置为 XVehicle。
  7. 实现类型应该为 Component(这是默认的)。

模型现在如图 7 所示。

图 7. searchVehicle
图像显示 Txn 事务面板以及 searchVehicle 属性

模型的定义现在已经完成,可以生成代码。验证模型,以检查有无信息缺失,然后点击 Generate implementation 生成骨架代码。

下面的内容将详细分析每个示例查询事务及其实现。


以 Business Proxy 方式实现查询事务

在此示例模型中,事务 getPartyAndVehicles 被实现为一个 Business Proxy。实现需要调用 getParty 与新事务 (getVehiclesByParty),将所有返回记录放入一个 PartyAndVehicles 对象,然后将这个对象作为响应返回。

当在模型中定义 Business Proxy 事务时,Business Proxy 的骨架代码被生成到 Java 包 .compositeTxn 中。为每个事务生成一个 Java 类,其类名称基于事务名称。

在这个例子中,类的名称为 GetPartyAndVehiclesCompositeTxnBP

Business Proxy 类扩展了基类 com.dwl.base.requestHandler.DWLTxnBP。事务逻辑应该在以下方法中进行编码。

清单 5. Business Proxy 执行方法签名
public Object execute(Object inputObj) throws BusinessProxyException
{
}

以参数形式传入的实际对象依赖于事务接口风格。它可以是以下对象之一:

  • com.dwl.base.requestHandler.DWLTransactionInquiry,用于 Inquiry 风格
  • com.dwl.base.requestHandler.DWLTransactionPersistent,用于修改数据库的 Txn 风格事务
  • com.dwl.base.requestHandler.DWLTransactionSearch,用于 Txn 风格的查询

方法返回的对象应该是 com.dwl.tcrm.common.TCRMResponse 的一个实例。

要从 Business Proxy 调用其他事务,您应该创建 DWLTransactionInquiryDWLTransactionPersistentDWLTransactionSearch 的实例,并把它们传递给 MDM Server 框架进行执行。这确保事务的执行方式与直接调用它完全相同,而且框架在正确的点介入处理事务审计日志记录、验证与其他平台服务。

要实现示例 getPartyAndVehicles 事务,您需要获取事务参数值(在这个例子中,为字符串 partyId),如下所示。

清单 6. 提取查询参数
/**
* @generated NOT
**/
   public Object execute(Object inputObj) throws BusinessProxyException {
   DWLTransactionInquiry inputTxnObj = (DWLTransactionInquiry) inputObj;
   DWLControl control = inputTxnObj.getTxnControl();
   // Extract the request parameters. These will appear in the order
   // supplied.
   Vector parameters = inputTxnObj.getStringParameters();
   String partyId = (String)parameters.get(0);
  …

注意:记住在开始编辑时尽快标记方法 @generated NOT

现在您有了 partyId,下一步是调用 getParty。Transaction Reference Guide 提供了每种事务的必需参数的相关信息。在这个例子中,参数包括 partyIdPartyTypeInquiryLevel。您拥有 partyId。不知道 PartyType,但是由于它是一个可选参数,因此允许为空。InquiryLevel 确定返回详细信息的数量,将此设为 0,表示只获取基本的客户记录。

清单 7. 调用 getParty
// Handle transaction "getParty"
Vector getPartyInput = new Vector();
getPartyInput.add(partyId);
getPartyInput.add(null);
getPartyInput.add("0");

// Prepare a new DWLTransactionInquiry instance.
DWLTransactionInquiry getPartyRequest = new DWLTransactionInquiry();
getPartyRequest.setTxnControl(control);
getPartyRequest.setTxnType("getParty");
getPartyRequest.setStringParameters(getPartyInput);

// Invoke the "getParty" transaction.
DWLResponse getPartyResponse = (DWLResponse)
      super.execute(getPartyRequest);

注意:所有参数均以字符串形式传递,即使它们实际上是数字。参数的顺序必须正确。

如果调用 getParty 成功,下一步是调用 getVehiclesByParty。这需要一个参数,这个参数依旧是 partyId

清单 8. 调用 getVehiclesByParty
// Handle transaction "getVehiclesByParty"
Vector getVehiclesByPartyInput = new Vector();
getVehiclesByPartyInput.add(partyId);

// Prepare a new DWLTransactionInquiry instance.
DWLTransactionInquiry getVehiclesByPartyRequest = new
        DWLTransactionInquiry()
getVehiclesByPartyRequest.setTxnControl(control);
getVehiclesByPartyRequest.setTxnType("getVehiclesByParty");
getVehiclesByPartyRequest.setStringParameters(getVehiclesByPartyInput);

// Invoke the "getVehiclesByParty" transaction.
DWLResponse getVehiclesByPartyResponse = (DWLResponse)
        super.execute(getVehiclesByPartyRequest);

接下来,必须构造一个 PartyAndVehicles 对象的实例来包含返回的客户和车辆记录。

清单 9. 创建响应对象
PartyAndVehiclesBObj mainOutput = new PartyAndVehiclesBObj();
mainOutput.setControl(control);
// add party record
if( getPartyResponse != null ){
   	mainOutput.setTCRMPartyBObj( \
(TCRMPartyBObj) 					getPartyResponse.getData() );
}
// add vehicle records
if( getVehiclesByPartyResponse != null ){
    Vector<XVehicleBObj> getVehiclesByPartyOutput =\
 	(Vector<XVehicleBObj>) getVehiclesByPartyResponse.getData();
    if( getVehiclesByPartyOutput != null ){
    	for( XVehicleBObj vehicle: getVehiclesByPartyOutput ){
    		mainOutput.setXVehicleBObj(vehicle);
     	}
    }
}

最后,可以构造并返回响应。

清单 10. 返回响应
// Construct the response object.
DWLStatus outputStatus = new DWLStatus();
outputStatus.setStatus(DWLStatus.SUCCESS);
TCRMResponse outputTxnObj = new TCRMResponse();
outputTxnObj.setStatus(outputStatus);
outputTxnObj.setData(mainOutput);
return outputTxnObj;

为了简单起见,我在这里省略了所有错误处理的代码(参见 下载 中完整的源代码)。

实现 Txn 风格的接口事务与此类似。主要区别在于,执行方法的参数是 DWLTransactionSearchDWLTransactionPersistent 的一个实例,而非 DWLTransactionInquiry 的实例。您可以调用 getTxnTopLevelObject() 方法来获取请求 BObj。

请求 BObj 有一个相关的 DWLControl 对象,代表请求的控制头。当您构造新的 BObj 时,使用原始请求的 DWLControl 调用它上面的 setControl() 方法。


自定义一个 Get Record 事务

一个 Get Record 事务需要生成大量的代码,因此看起来可能有些复杂,但实际上,这是针对新实体实现查询的最简单方式。

生成的代码是可执行的,无需进行任何自定义。如果您部署为 VehicleInfo 模型生成的代码并添加了一些车辆记录,那么可以尝试运行 getXVehiclegetVehiclesByLicense 事务。

默认的 get 事务 (getXVehicle) 用于按照主键查询记录,通常不是自定义的。

为查询 getVehiclesByLicense 生成的实现将返回与传递的车牌号码值相匹配的所有记录。如果需要,自定义这类查询是很简单的事情。您可以在不修改任何其他代码的情况下任意修改查询,但要了解以下规则:

  • 参数的类型与顺序必须相同,但您可以修改它们在 SQL 中使用方式。
  • 结果集中的列被映射到结果对象中的字段。不要改变顺序或者增加或删除结果列。假定对结果对象的映射不受影响,您可以通过不同方式得到结果列。

最有可能出现的一种自定义方法是修改 WHERE 子句以用不同的方式使用参数。为此,您需要为定义 Get Record 事务的实体编辑 InquiryData 类。在这个例子中,这个类是 VehicleInfo 项目中的 XVehicleInquiryData 类。所有 InquiryData 类都将被生成到 .entityObject 包中。

还为每个 Get Record 事务生成两条 SQL 语句。您可以在 InquiryData 类中定义的静态字段中找到它们。例如,在 XVehicleInquiryData 中,您应该看到:

清单 11. 生成的 SQL
 /**
 * <!-- begin-user-doc -->
 * <!-- end-user-doc -->
 *
 * @generated
 */
 static final String getVehiclesByLicenseSql = "SELECT r.XVehiclepk_Id XVehiclepk_Id, 
 r.license_Number license_Number, \
r.vehicle_Type vehicle_Type, r.owning_Party owning_Party,
 r.LAST_UPDATE_DT LAST_UPDATE_DT, \
r.LAST_UPDATE_USER LAST_UPDATE_USER, r.LAST_UPDATE_TX_ID 
 LAST_UPDATE_TX_ID FROM XVEHICLE r WHERE r.license_Number = ? ";

还有一个类似的字段名为 getVehiclesByLicenseHistorySql

History SQL 是在 inquireAsOfDate 包含在请求头中时用于查询,目的是查询历史记录而非最新记录。如果您不需要支持历史查询,可以忽略它。

getVehiclesByLicense 查询进行非常简单更改可允许它支持车牌号码中的通配符,使功能更加强大。

更改字段 getVehiclesByLicenseSql,如下所示。

清单 12. 自定义的 SQL
/**
   * <!-- begin-user-doc -->
   * <!-- end-user-doc -->
   *
   * @generated NOT
   */
  static final String getVehiclesByLicenseSql = "SELECT r.XVehiclepk_Id XVehiclepk_Id, 
  r.license_Number license_Number, \
r.vehicle_Type vehicle_Type, r.owning_Party owning_Party,
  r.LAST_UPDATE_DT LAST_UPDATE_DT, \
r.LAST_UPDATE_USER LAST_UPDATE_USER, r.LAST_UPDATE_TX_ID 
  LAST_UPDATE_TX_ID FROM XVEHICLE r WHERE r.license_Number LIKE ? ";
  1. WHERE 子句中,将 = 更改为 LIKE。这允许在参数中使用通配符。并且在字段上方的注释中,将 @generated 注释更改为 @generated NOT。当从模型再次生成代码时,这将保留您所做的更改。
  2. 保存文件。
  3. 现在返回 VehicleInfo 模型并再次生成代码。 这将从 XVehicleInquiryData 接口重新生成 XVehicleInquiryDataImpl 类。

.entityObject 包中的每个数据类都有一个相应的 DataImpl 类,是实现 SQL 语句的 pureQuery 生成的代码。在任何时候更改数据接口时,您都需要确保重新生成相应的 DataImpl 类。从模型运行代码生成可以实现此目标。


使用动态 SQL 实现一个搜索事务

最后一个要实现的、也是最复杂的示例事务是 searchVehicles。这是一个 Component 方法实现的 Txn 风格的事务,将使用动态 SQL 对车辆记录执行搜索。

搜索参数包括客户姓名(人的姓名或组织名称)、客户类型(P 或 O)、车牌号码和车辆类型编码。必须提供客户姓名或车牌号码。如果使用客户姓名,则必须指定客户类型,否则可以忽略。车辆类型是一个可选参数。在它的响应中,searchVehicles 将返回一个车辆记录的列表 (XVehicleBObj)。

如果您正搜索的记录包含大量数据,那么搜索返回的结果数量通常也会很大,您可能想返回一个包含摘要信息而非完全记录的临时性数据对象,从而避免序列化与反序列化客户端永远不会使用的数据所带来的开销。

在这个例子中,车辆记录很小,因此可以作为搜索结果直接返回。这种设计还可以简化实现,因为查询车辆记录所生成的代码可以重用。

Component 方法的事务实现以 Component 类开始,在这个例子中是 .component Java 包中的 VehicleInfoComponent

在 VehicleInfo 文件夹下进行建模的每个 Component 方法事务均由这个类实现。对于每个事务,您将看到生成了两个方法(在此例中):

清单 13. Component 方法签名
public DWLResponse searchVehicle(XVehicleSearchRequestBObj \
theBObj) throws DWLBaseException
public DWLResponse handleSearchVehicle(XVehicleSearchRequestBObj \
theBObj) throws Exception {

事务逻辑的实现应该始终在句柄方法中进行编码,并在验证请求对象后由 MDM Server 框架进行调用。

handleSearchVehicle 方法生成的只有骨架代码。复制 handleGetVehiclesByLicense 方法的实现,并将它粘贴到句柄 SearchVehicles 中,然后编辑代码,如清单 14 所示。

清单 14. handleSearchVehicle 方法体
public DWLResponse handleSearchVehicle(XVehicleSearchRequestBObj \
theBObj) throws Exception {
   DWLStatus status = new DWLStatus();
   DWLResponse response = createDWLResponse();
   DWLControl control = theBObj.getControl();
   // create BObjQuery
BObjQuery bObjQuery = getBObjQueryFactory().createXVehicleBObjQuery(
XVehicleBObjQuery.SEARCH_VEHICLES, control); // pass query parameters to BObjQuery bObjQuery.setParameter("licenseNumber", theBObj.getLicenseNumber(), \ java.sql.Types.VARCHAR); bObjQuery.setParameter("partyName", theBObj.getPartyName(), java.sql.Types.VARCHAR); bObjQuery.setParameter("partyType", theBObj.getPartyType(), java.sql.Types.VARCHAR); bObjQuery.setParameter("vehicleType", theBObj.getVehicleTypeType(), \ java.sql.Types.BIGINT); // ask framework to handle pagination boolean considerForPagination = PaginationUtils
.considerForPagintion(XVehicleBObj.class.getName(), control); control.setConsiderForPagintionFlag(considerForPagination); // run the query List list = bObjQuery.getResults(); // handle the results if (list.size() == 0) { return null; } Vector vector = new Vector(); for (Iterator it = list.iterator(); it.hasNext();) { XVehicleBObj o = (XVehicleBObj) it.next(); // fill in type code values postRetrieveXVehicleBObj(o, "0", "ALL", control); vector.add(o); if (o.getStatus()==null) { o.setStatus(status); } response.addStatus(o.getStatus()); } response.setData(vector); return response; }

注意:记住在开始编辑之前标记方法 @generated NOT

事实上,这段代码不会编译,因为常量 XVehicleBObjQuery.SEARCH_VEHICLES 尚未定义,而且您可能已经注意到,看不到任何 SQL,因此需要进一步做一些工作。

Component 方法将实际的查询执行委托给一个 BObjQuery. At 类型的对象:

BObjQuery bObjQuery = getBObjQueryFactory().createXVehicleBObjQuery(		
  XVehicleBObjQuery.SEARCH_VEHICLES, control);

bObjQuery 实际上是 XVehicleBObjQuery 的一个实例,它是 .bobj.query 包中一个生成的类。是这个类确定了要执行的 SQL 和处理结果的方式。

要构造一个 BObjQuery,您需要提供一个常量表示要执行查询的类型。在 XVehicleBObjQuery 类中声明新常量 XVehicleBObjQuery.SEARCH_VEHICLESpublic static final String SEARCH_VEHICLES = "SEARCH_VEHICLES";。实际的值并不要紧,只要它在这个类中是惟一的。

对于其他生成的查询事务,您在 BObjQuery 类中看不到任何 SQL。这是因为由基类 GenericBObjQuery 处理的默认行为是从 .entityObject 包中的 InquiryData 接口类获取 SQL。pureQuery 生成的 DataImpl 类处理 SQL 的执行,并将结果转换为 Java 对象。

对于新的搜索事务,必须动态构造 SQL,而不能使用来自 XVehicleInquiryData 接口类的静态 SQL,因此您将不得不改写 GenericBObjQuery 的操作方式。首要任务是弄清楚需要哪些 SQL 来实现搜索,以及如何动态构造它。

XVehicleBObjQuery 类中,定义以下静态字符串,用于将所需的 SQL 拼接在一起。

清单 15. 搜索 SQL 片段
static final String baseXVehicleSql = "SELECT DISTINCT \
r.XVehiclepk_Id XVehiclepk_Id, r.license_Number license_Number,
r.vehicle_Type vehicle_Type, r.owning_Party owning_Party, \
r.LAST_UPDATE_DT LAST_UPDATE_DT, r.LAST_UPDATE_USER LAST_UPDATE_USER, 
r.LAST_UPDATE_TX_ID LAST_UPDATE_TX_ID FROM XVEHICLE r";

static final String personNameFrom = ", PERSONNAME n";
static final String orgNameFrom = ", ORGNAME n";
static final String personNameWhere = " r.owning_Party = \
n.CONT_ID  AND n.LAST_NAME LIKE ?";
static final String orgNameWhere = " r.owning_Party = n.CONT_ID  AND n.ORG_NAME LIKE ?";
static final String licenseWhere = " r.license_Number LIKE ?";
static final String vehicleTypeWhere = " r.vehicle_Type = ?";

baseXVehicleSQL 中结果列的定义是从 XVehicleInquiryData.getXVehicleSQL 复制而来。这很重要,因为它允许您重用 getXVehicle 查询中生成的 pureQuery 结果处理代码来处理搜索结果。

现在来构造 SQL 语句,如下重写基类方法 getSQLStatement()

清单 16. getSQLStatement
@Override
protected String getSQLStatement() throws BObjQueryException {
   // override just the SEARCH_VEHICLES query
   if( queryName.equals(SEARCH_VEHICLES)){
    	StringBuffer buf = new StringBuffer();
    	buf.append(baseXVehicleSql);
      // get the parameter values
    	SQLParam partyType = (SQLParam) namedParams.get("partyType");
    	SQLParam partyName = (SQLParam) namedParams.get("partyName");
	SQLParam licenseNumber = (SQLParam)
namedParams.get("licenseNumber"); SQLParam vehicleType = (SQLParam) namedParams.get("vehicleType"); // if partyName is supplied if( partyType.getValue() != null
&& partyName.getValue() != null ){ if( partyType.getValue().equals("P")){ buf.append(personNameFrom); } else if( partyType.getValue().equals("O")){ buf.append(orgNameFrom); } } // start WHERE clause buf.append(" WHERE"); // count parameters to SQL int paramCount = 0; // if partyName is supplied if( partyType.getValue() != null
&& partyName.getValue() != null ){ if( partyType.getValue().equals("P")){ buf.append(personNameWhere); } else if( partyType.getValue().equals("O")){ buf.append(orgNameWhere); } // set positional parameter setParameter(paramCount, partyName.getValue()); paramCount++; // append parameter index to SQL, like ?1 buf.append( paramCount ); } if( licenseNumber.getValue() != null ){ if( paramCount == 1){ // append AND if there is already a parameter buf.append(" AND"); } buf.append(licenseWhere); // set positional parameter setParameter(paramCount, licenseNumber.getValue()); paramCount++; // append parameter index to SQL, like ?1 buf.append( paramCount ); } if( vehicleType.getValue() != null ){ buf.append(" AND"); buf.append(vehicleTypeWhere); // convert string to long Long vehicleTpCd = Long.valueOf((String)vehicleType.getValue()); // set positional parameter setParameter(paramCount, vehicleTpCd); paramCount++; // append parameter index to SQL, like ?1 buf.append( paramCount ); } return buf.toString(); } // all other queries, default behavior return super.getSQLStatement(); }

注意:在这段代码中,如何将数字插入到 SQL 中以告诉 pureQuery 要使用参数的位置。

最后,确保结果得到正确的处理。您可以获得现有生成的代码,通过告诉框架以与主键事务的标准 get 相同的方式处理这个新查询的结果,从而将结果集转换为 Java 对象。只要新查询返回的是相同的结果列,就没有问题。

改写方法provideRowHandler,如清单 17 中所示。

清单 17. proveRowHandler
@Override
protected RowHandler provideRowHandler(Class queryInterfaceClass) \
throws BObjQueryException {
    if( queryName.equals(SEARCH_VEHICLES)){
    	Object query;
	try {
   // get the row handler for getXVehicle()
	   query = DataAccessFactory.getInquiry(queryInterfaceClass);
	   return DataAccessFactory.getRowHandler(query, XVEHICLE_QUERY);
	} catch (ServiceLocatorException e) {
         throw new BObjQueryException(e);
	}
    }
    // for all other queries, default behavior
    return super.provideRowHandler(queryInterfaceClass);
}

搜索事务的基本实现已经完成,但还可以编写另外的代码来验证请求参数和处理错误。

要自行将结果集转换为 Java 对象,您应该改写 provideSQLStatement() 而非 getSQLStatement(),并在 ResultSetProcessor 类中添加您的结果处理代码,而不是改写 provideRowHandler()


结束语

在这份指南中,您已学会了如何:

  • 以 Business Proxy 方式实现一个查询事务。
  • 使用一个 Get Record 事务在自定义实体上实现简单的查询。
  • 为自定义实体实现一个简单的搜索事务。

下载

描述名字大小
样例脚本vehicle.zip245KB

参考资料

学习

获得产品和技术

讨论

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Information Management
ArticleID=791570
ArticleTitle=实现 IBM InfoSphere Master Data Management Server 的自定义查询事务
publish-date=02062012