DB2 XML 编程,第 4 部分: 在基于 Web 的 DB2 应用程序中集成来自不同数据源的数据

11 篇文章 0 订阅

简介

本文为 DB2 提供一个 SOAP 驱动程序示例,以便使用 SOAP API 隐式地执行 DB2 存储过程,而不需要创建任何显式的映射。我相信下一代数据库驱动程序会让数据库连接抛弃 ODBC/JDBC 等低层 API,转而使用 SOAP 和 REST 等高层 API,这会使数据库成为 SOA 环境中的直接参与者。

为了突出这个体系结构中与 XML 数据模型相关的方面,示例程序的流程尽可能保持简单。GUI 也保持展示功能所需的最基本形式。

我使用 Open Travel Alliance XML 模式创建旅馆示例数据和搜索旅馆的 SOAP 调用。使用 PayPal API 处理来自应用服务器的信用卡交易。

本文后面提供了源代码,可以下载并编译这些源代码。您需要安装 DB2 9 并在 Tomcat 类路径中包含 DB2 JCC 和 XML jar 文件。如果希望测试信用卡交易,就需要安装 PayPal Java API 并在 Tomcat 类路径中包含相关的 jar 文件。还必须在 PayPal 沙箱中创建一个帐户并获得 API 凭证,详细说明参见 PayPal Integration Center。然后可以修改 article4.java 文件的 setupPaypal() 函数中的凭证信息。

场景

在这个场景中,一位客户要通过 Web 预订旅馆房间。他首先要登录,获得他的个人信息。然后,指定一个城市,获取这个城市中的旅馆及其房间价格列表。最后,选择一家旅馆并预订一个房间。


图 1. 特性级体系结构
特性级体系结构

客户机上的客户操作导致 Web 浏览器向应用服务器发出 REST 调用。然后,应用服务器:

  • 使用 JDBC 直接连接内部数据库,获取客户的个人信息。
  • 对另一个数据库执行 SOAP 调用,这个数据库在公司防火墙内,但是位于客房预订部门的内部防火墙后面。
  • 对一个外部信用卡交易服务提供商(比如 PayPal)执行 REST 调用。


图 2. 设计级体系结构
设计级体系结构

细节

为了了解在预订过程的每个步骤幕后发生的情况,我们来看看信息流和相关代码。

步骤 1

客户在旅行代理商的 Web 站点上输入他的姓名并获得他的个人信息。为了简单,这个示例不要求输入密码,并假设客户个人信息已经在代理商数据库中存在。


图 3. 登录和获得个人信息的命令和数据流
登录和获得个人信息的命令和数据流

客户个人信息是一个 XML 文档,存储在数据库中的 XML 列中。信用卡信息也是一个 XML 文档,但是为了安全,它被加密并存储为二进制格式。


清单 1. 创建 customers 表并插入记录

CREATE   TABLE  CUSTOMERS (CUSTID  CHARACTER  ( 64 NOT   NULL ,
CC 
VARCHAR ( 1024 for   bit  data  not   null , INFO XML  NOT   NULL  )

insert   into  CUSTOMERS  values ( ' hardeep ' ,
encrypt(
' <CC type="visa" expirydate="12/2009" number="4721930402892796" cvv="808">
<name>hardeep singh</name></CC>
'  ,  ' password ' ),
' <Customer customerid="hardeep" firstname="hardeep" lastname="singh"/> ' );

图 4. 登录
登录

当单击 Login 按钮时,在客户机中调用 Javascript 函数 getCustomerInfo()。这个函数生成执行应用服务器中的 customerinfo 服务所需的 REST 调用。


清单 2. 用来获取客户个人信息的客户机调用

var  cid = document.getElementById( " userid " ).value;
var  addr = servletpath + " ?cmd=customerinfo&msg= " + cid;
var  xmlhttpObj =   new  XMLHttpRequest();
xmlhttpObj.open(
' GET ' , addr,  true );
xmlhttpObj.onreadystatechange 
=   function ()  { getCustomerInfoCallback(xmlhttpObj); } ;
xmlhttpObj.send(
"" );

应用服务器对本地数据库执行一个 SQL 查询,从 customers 表的 info 列中选择客户个人信息。


清单 3. 应用服务器查询数据库来获取客户个人信息

Connection conn =  DriverManager.getConnection( " jdbc:db2:article4 " );
Statement stmt 
=  conn.createStatement();
stmt.setMaxRows(
1 );
ResultSet rs
=  stmt.executeQuery(
" select info from customers where custid=' " + msg + " ' " );
if (rs.next ()) retValue = rs.getString( 1 );
stmt.close();
conn.close();

将数据库查询所产生的客户数据以 XML 数据的形式发送回客户机。


清单 4. 在 HTTP 报头中返回的数据类型设置为 XML

_res.setContentType( " text/xml " );
 _res.setHeader(
" Cache-Control " " no-cache " );
_res.getWriter().write(retValue);

当客户机从应用服务器接收到客户数据时,它调用 getCustomerInfoCallback 函数,这个函数使用 XMLParse 包装器类将客户 XML 数据解析为 DOM 树并保存在一个全局变量中。然后改变用户界面,让客户能够输入城市的编码。


清单 5. 解析 XML 数据并改变用户界面

customerinfo =   new  xmlparse(xmlhttp.responseXML,  false );
var  hstr = ' <table cellSpacing="0" width="100%" cellPadding="2" border="0" align="left"> ' ;
hstr
+= ' <tr><td align="right">City Code:</td><td><INPUT type="text" id="citycode"
SIZE=15 MAXLENGTH=50 value="msy" tabindex="1">
' ;
hstr
+= ' <td><INPUT type=button value="submit" onClick="javascript:getRates()" > ' ;
document.getElementById(
" canvas " ).innerHTML = hstr;

步骤 2

客户现在要搜索旅行目的地城市中的旅馆。


图 5. 列出旅馆及其房间价格的命令和数据流
列出旅馆及其房间价格的命令和数据流

用户输入城市编码并单击提交按钮,从而调用客户机 Javascript 中的 getRates() 函数。


图 6. 搜索与城市编码对应的旅馆
搜索与城市编码对应的旅馆

getRates 函数使用 OTA_HotelAvailRQ XML 模式生成请求旅馆信息所需的应用服务器调用。


清单 6. 使用 OTA_HotelAvailRQ 模式创建的搜索旅馆消息

var  citycode = document.getElementById( " citycode " ).value
var  req = ' OTA_HotelSearchRQ xmlns="http://www.opentravel.org/OTA/2003/05"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.opentravel.org/OTA/2003/05 OTA_HotelSearchRQ.xsd"
EchoToken="HL" Target="Production" Version="1.003" PrimaryLangID="EN-US"
ResponseType="PropertyList">
' +
' <POS><Source AirlineVendorID="FG" PseudoCityCode="MIA" ISOCountry="US"
ISOCurrency="USD" AgentSine="A4444BM" AgentDutyCode="FR"></Source>
' +
' <Source><RequestorID Type="5" ID="12345675" ID_Context="IATA"/></Source></POS> ' +
' <Criteria><Criterion><RefPoint></RefPoint><CodeRef LocationCode="23"
 CodeContext="OTA-REF code list"/>
' +
' <HotelRef HotelCityCode=" ' + citycode + ' "/><Radius Distance="2" DistanceMeasure="MILES"/> ' +
' <RoomAmenity RoomAmenity="74"/><RoomAmenity RoomAmenity="123"/></Criterion>
</Criteria></OTA_HotelSearchRQ>
' ;

使用 AJAX API 将旅馆清单调用以 POST 请求的形式发送到应用服务器。


清单 7. 发送到应用服务器的搜索旅馆消息

var  msg = ' <request cmd="hotelrates"> ' + req + ' </request> ' ;
var  xmlhttpObj =   new  XMLHttpRequest();
xmlhttpObj.open(
' POST ' , servletpath,  true );
xmlhttpObj.onreadystatechange 
=   function ()  { getRatescallback(xmlhttpObj); } ;
xmlhttpObj.setRequestHeader(
' content-type ' ' text/xml ' );
xmlhttpObj.send(msg);

应用服务器创建一个 SOAP 调用,调用 article4 数据库中的 getHotelRates 存储过程。这个数据库在部门内部防火墙的后面运行,位置是 http://localhost:8080/article4。从客户机接收的 OTA 搜索旅馆请求(msg)作为参数传递给这个存储过程。

注意:SOAP 消息的 SOAPAction 属性设置为数据库名。


清单 8. 应用服务器对 DB2 getHotelRates 存储过程执行一个 SOAP 调用

String body = " <db:getHotelRates xmlns:db='http://ibm.com/db2/soap'> " +
" <db:arg> " + msg + " </db:arg></db:getHotelRates> " ;
return  sendURLMessage( " http://localhost:8080/article4/db2soapdriver " ,
body,
" http://ibm.com/db2/soap#article4 " );

尽管有用来创建 SOAP 消息的 API,但是本文只使用基本的 URL 调用代码,以此说明 SOAP 调用仅仅是一种特殊的 HTTP POST 调用,其消息体符合一个标准化的 XML 模式。

注意:SOAPAction 设置为目标数据库名。


清单 9. 应用服务器对 DB2 getHotelRates 存储过程执行一个 SOAP 调用

URL u  =   new  URL(url);
URLConnection uc 
=  u.openConnection();
HttpURLConnection connection 
=  (HttpURLConnection) uc;
connection.setDoOutput(
true );
connection.setDoInput(
true );
connection.setRequestMethod(
" POST " );
connection.setRequestProperty(
" SOAPAction " , database_name);
OutputStream out 
=  connection.getOutputStream();
Writer wout 
=   new  OutputStreamWriter(out);
 wout.write(
" <?xml version='1.0'?> " );
>
wout.write(
" xmlns:SOAP-ENV= " );
wout.write(
" 'http://schemas.xmlsoap.org/soap/envelope/'  " );
wout.write(
" xmlns:SOAP-ENC= " );
wout.write(
" 'http://schemas.xmlsoap.org/soap/encoding/'  "  );
wout.write(
" SOAP-ENV:encodingStyle= " );
wout.write(
" 'http://schemas.xmlsoap.org/soap/encoding/'  " );
wout.write(
" xmlns:xsi= " );
wout.write(
" 'http://www.w3.org/2001/XMLSchema-instance'>  " );
wout.write(
"  <SOAP-ENV:Body> " );
wout.write(msg);
wout.write(
"  </SOAP-ENV:Body> " );
 wout.write(
" </SOAP-ENV:Envelope> " );

因为应用服务器仅仅创建 SOAP 包装器并执行一个 URL 调用,所以如果 AJAX 的安全限制允许的话,也可以从客户机直接执行 SOAP 调用。尽管在 Web 客户机中使用 SOAP 驱动程序直接调用数据库是可能的,但是由于安全原因这种方式并不合适,应该改进 SOAP 驱动程序来防止这种做法。

 

getHotelRates 存储过程接受一个 XML 参数,其中包含 OTA 请求。XQuery 从输入的 XML 中提取出 HotelCityCode,并用它搜索和列出包含匹配的 HotelCityCode 属性的所有旅馆。


清单 10. getHotelRates 存储过程

CREATE   PROCEDURE  getHotelRates(  IN  request XML )
DYNAMIC RESULT SETS 
1
LANGUAGE SQL
BEGIN
DECLARE  c_cur  CURSOR   WITH   RETURN   FOR
Select  XMLQuery( ' declare namespace ns1 = "http://www.opentravel.org/OTA/2003/05";
$info//ns1:HotelDescriptiveContents
'  passing info  as  "info")
from  hotel
where  xmlexists( ' declare namespace ns1 = "http://www.opentravel.org/OTA/2003/05";
$info//ns1:HotelDescriptiveContents[@HotelCityCode=$req//ns1:HotelRef/@HotelCityCode]
'
passing request 
as  "req", info  as  "info" );
OPEN  c_cur;
END

然后,将从 db2soapdriver 返回给应用服务器的 SOAP 响应发送回客户机,而不做任何修改。这再次说明,在 XML 模型编程方式中,数据库成了重要的参与者,而且在许多情况下应用服务器仅仅作为交换信息的中介。

当客户机从应用服务器接收到响应时,它调用 getRatescallback。使用 DOM 解析器解析返回的 SOAP 响应。DOM 解析器会处理 SOAP 响应中的名称空间。


清单 11. 客户机解析来自应用服务器的 SOAP 响应

soapxml =   new  xmlparse(xmlhttp.responseXML,  false );
soapxml.xmlRoot.setProperty(
" SelectionNamespaces " ,
" xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:SOAP-ENV='http://schemas.xmlsoap.org/soap/envelope/'
xmlns:db='http://ibm.com/db2/soap'
" );

var  hstr = ' <table cellSpacing="0" width="100%" cellPadding="2" border="1" align="left"> ' ;
hstr
+= " <tr><td>name<td>rate<td>rooms<td> " ;

将数据库结果中的每一行提取到另一个 DOM 树中,然后使用 XPath 提取相关信息。为客户机创建一个新视图,显示返回的所有旅馆的列表。注意,XPath 调用中使用了一个名称空间别名。


清单 12. 客户机从 SOAP 体中提取数据库结果集

soapxml.find( " //SOAP-ENV:Body//db:row " , null , true );
for (i = 0 ;soapxml.currentFind.length > i;i ++ )
{
var result=soapxml.getValue("db:col/text()",i);
rateslist
=new xmlparse(result,true);
rateslist.xmlRoot.setProperty(
"SelectionNamespaces",
"xmlns:xsl='http://www.w3.org/1999/XSL/Transform'
xmlns:x='http://www.opentravel.org/OTA/2003/05' 
");
 
 
var id=rateslist.getValue("//x:HotelDescriptiveContents/@HotelCode",null);
 
var name=rateslist.getValue("//x:HotelName/@HotelShortName",null);
 
var rooms=rateslist.getValue("//x:GuestRoomInfo/@Quantity",null);
 
var charge=rateslist.getValue("//x:Charge/@Amount",null);
 
hstr
+="<tr><td>"+name+"<td>"+charge+"<td>"+rooms+"<td>
<input type='button' onClick="javascript:bookRoom('
"+id+"','"+charge+"');
  " value='select'/>
";
}

document.getElementById(
" canvas " ).innerHTML = hstr;

步骤 3

在最后一步中,客户选择一家旅馆并预订房间。从数据库中保存的客户个人信息中获得信用卡信息。


图 7. 预订房间的命令和数据流
预订房间的命令和数据流

客户现在可以在列表中选择一家旅馆,从而在这家旅馆预订房间。


图 8. 从旅馆列表中选择旅馆
从旅馆列表中选择旅馆

当客户单击 Select 时,调用客户机 Javascript 中的 bookroom 函数。使用 AJAX API 将一个 XML 消息(请求预订房间)以 POST 请求的形式发送到应用服务器。这个请求包含旅馆 ID、客户名、要预订的房间数量和客户信用卡需要支付的数额。


清单 13. 客户机向应用服务器发送 REST 调用来预定房间

function  bookRoom(hotelid,amount)
{
var cid=document.getElementById("userid").value;
var msg='<request cmd="bookroom"><message><ccinfo units="1" invoice=""
amount="
'+amount+'"/><username>'+cid+'</username>
<hotelid>
'+hotelid+'</hotelid></message></request>';
var xmlhttpObj= new XMLHttpRequest();
xmlhttpObj.open(
'POST', servletpath, true);
xmlhttpObj.onreadystatechange 
= function() { bookRoomcallback(xmlhttpObj); };
xmlhttpObj.setRequestHeader(
'content-type''text/xml');
xmlhttpObj.send(msg);
}

应用服务器解析收到的消息并从其中提取出客户 ID。然后,调用本地数据库,从 customers 表中获取信用卡信息和客户个人信息。

注意:信用卡信息存储在 customers 表的一个加密列中。尽管在这个示例中密码是硬编码的,但是在真实的场景中密码可能是客户用来登录的密码。


清单 14. 应用服务器向数据库查询加密的信用卡信息

XMLParse msgxml = new  XMLParse(msg);
String userid
= msgxml.getValue( " //username/text() " );
Connection conn
=  DriverManager.getConnection( " jdbc:db2:article4 " );
Statement stmt 
=  conn.createStatement();
stmt.setMaxRows(
1 );
ResultSet rs
=  stmt.executeQuery( " select info,
decrypt_char(CC, ' password ' ) from customers where custid = ' "+userid+" '" );
if (rs.next ())
{
String custinfo
=rs.getString(1);
String CCInfo
=rs.getString(2);

下载

描述名字大小下载方法
本文的源代码code.zip42 KBHTTP
关于下载方法的信息


参考资料

学习

接下来,应用服务器创建一个 SOAP 调用,调用在部门内部防火墙后面运行 article4 数据库中的 bookaroom 存储过程。旅馆 ID 和客户个人信息作为参数传递给这个存储过程。


清单 15. 应用服务器对 bookaroom 存储过程执行一个 SOAP 调用

String hotelid = msgxml.getValue( " //hotelid/text() " );
String body
= " <db:bookaroom xmlns:db='http://ibm.com/db2/soap'> " +
" <db:arg> " + hotelid + " </db:arg> " +
" <db:arg> " + custinfo + " </db:arg> " +
" <db:arg></db:arg> " +
" </db:bookaroom> " ;
String soapstr
= sendURLMessage( " http://localhost:8080/article4/db2soapdriver " ,
body,
" http://ibm.com/db2/soap#article4 " );

bookaroom 存储过程只是一个伪过程,它总是在输出参数中返回一个固定的发票号。它的用途是说明 db2soapdriver 如何处理输出参数并在 SOAP 响应中表示输出参数。

注意:如果它是一个真正预订房间的存储过程,那么还需要日期和房间类型等信息。作为一个练习,您可以改进客户机代码,让它接收日期和房间信息,并生成 OTA 房间预订消息。还需要添加另一个表来处理预订信息。


清单 16. bookaroom 存储过程

create   procedure  bookaroom ( in  hotelid  varchar ( 12 ),  in  userinfo xml,
  out invoice 
varchar ( 64 ))
language SQL
begin
set  invoice  =   ' INV001 ' ;
return   1 ;
end

使用 DOM 包装器类将 SOAP 响应字符串解析为 DOM 对象,并使用 XPath 调用提取出发票号。请查看 db2soapdriver.java,了解 DB2 存储过程调用产生的 SOAP 响应的模式。


清单 17. 应用服务器从 SOAP 响应中提取发票号

XMLParse soapXML =   new  XMLParse( true );
soapXML.createDOM(soapstr,
false );
String namespaces
=   " SOAPENV=http://schemas.xmlsoap.org/soap/envelope/;
db2 = http: // ibm.com/db2/soap";
soapXML.setNamespaces(namespaces, " = " );
String invoice
= soapXML.getValue( " //db2:out/text() " , null );

在客户机消息中设置发票号并传递给信用卡交易函数。将信用卡交易的结果和发票信息发送回客户机。


清单 18. 应用服务器将信用卡交易的结果发送回客户机
                    
if(invoice!=null)
{
msgxml.setValue("//ccinfo/@invoice",invoice);
retValue="<invoice='"+invoice+"'>"+makePayment(msgxml,CCInfo)+"</invoice>";

在信用卡交易函数中,从客户机消息中提取出信用卡和购物车信息。


清单 19. 从 DOM 中提取信用卡和购物车信息
                   	  
XMLParse ccinfo=new XMLParse(cc);
String units=msg.getValue("//ccinfo/@units");
String invno=msg.getValue("//ccinfo/@invoice");
String amount=msg.getValue("//ccinfo/@amount");
String ctype=ccinfo.getValue("//CC/@type");
String cnumber=ccinfo.getValue("//CC/@number");
String cexpdate=ccinfo.getValue("//CC/@expirydate");
String ccvv=ccinfo.getValue("//CC/@cvv");
String cname=ccinfo.getValue("//CC/name/text()");
String note="Paid by "+cname+" for "+units+" room(s) ";

使用 PayPal 信用卡服务的名称-值对(NVP)API 将这些信息传递给服务。

NVP API 是 PayPal 的业务功能、风险管理和业务逻辑的简单接口。NVP API 最基本的使用方法是通过到 PayPal 服务器的 HTTPS 连接发送一个 NVP 字符串,然后处理响应(也是一个 NVP 字符串)。执行 NVP API 调用的基本步骤如下:

  1. 为特定的 API 方法构造一个请求参数字符串。
  2. 通过 HTTPS 连接向 PayPal 服务器发送这个参数字符串。
  3. 处理服务器响应中的 NVP。

清单 20. 对 PayPal Web 服务执行 NVP API 调用
                 
NVPEncoder encoder = new NVPEncoder();
encoder.add("METHOD","DoDirectPayment");
encoder.add("PAYMENTACTION","Sale");
encoder.add("AMT",amount);
encoder.add("CREDITCARDTYPE",ctype);
encoder.add("ACCT",cnumber);
encoder.add("CVV2",ccvv);
encoder.add("EXPDATE",cexpdate);
encoder.add("FIRSTNAME",cname);
encoder.add("CURRENCYCODE","USD");
 encoder.add("INVNUM",invno);
 encoder.add("CUSTOM",note);
String NVPString = encoder.encode();
String ppresponse = (String) caller.call(NVPString);
NVPDecoder resultValues = new NVPDecoder();
resultValues.decode(ppresponse);
String transactionId = (String)resultValues.get("TRANSACTIONID");
String amt = (String)resultValues.get("AMT");
String avsCode = (String)resultValues.get("AVSCODE");
String cvv2Match = (String)resultValues.get("CVV2MATCH");
String strAck = resultValues.get("ACK");
String strAckSMSG = resultValues.get("L_SHORTMESSAGE0");
String strAckLMSG = resultValues.get("L_LONGMESSAGE0");
String strAckLERR= resultValues.get("L_ERRORCODE0");
if(strAck !=null && !(strAck.equals("Success") || strAck.equals("SuccessWithWarning")))
{
return("PAYPAL Failed "+strAckLERR+" "+strAckSMSG+" "+strAckLMSG );
}

结束语

基于面向 Web 体系结构的应用程序可以使用 XML 数据模型减少设计和代码的复杂性。当使用 SOAP 和 REST 等高层 API 访问数据库时,能够轻松地集成来自不同数据库的数据。在当今的企业环境中经常出现合并和安全事件,所以以这种黑盒方式将公司数据隐藏在 Web 服务背后已经成为一项关键需求。XML 是连接这种体系结构中各个组件的桥梁,而 XML 数据模型是这个环境中最自然的编程模型。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值