本文为 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 预订旅馆房间。他首先要登录,获得他的个人信息。然后,指定一个城市,获取这个城市中的旅馆及其房间价格列表。最后,选择一家旅馆并预订一个房间。
客户机上的客户操作导致 Web 浏览器向应用服务器发出 REST 调用。然后,应用服务器:
- 使用 JDBC 直接连接内部数据库,获取客户的个人信息。
- 对另一个数据库执行 SOAP 调用,这个数据库在公司防火墙内,但是位于客房预订部门的内部防火墙后面。
- 对一个外部信用卡交易服务提供商(比如 PayPal)执行 REST 调用。
为了了解在预订过程的每个步骤幕后发生的情况,我们来看看信息流和相关代码。
客户在旅行代理商的 Web 站点上输入他的姓名并获得他的个人信息。为了简单,这个示例不要求输入密码,并假设客户个人信息已经在代理商数据库中存在。
客户个人信息是一个 XML 文档,存储在数据库中的 XML 列中。信用卡信息也是一个 XML 文档,但是为了安全,它被加密并存储为二进制格式。
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"/> ' );
当单击 Login 按钮时,在客户机中调用 Javascript 函数 getCustomerInfo()。这个函数生成执行应用服务器中的 customerinfo 服务所需的 REST 调用。
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 列中选择客户个人信息。
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.setHeader( " Cache-Control " , " no-cache " );
_res.getWriter().write(retValue);
当客户机从应用服务器接收到客户数据时,它调用 getCustomerInfoCallback 函数,这个函数使用 XMLParse 包装器类将客户 XML 数据解析为 DOM 树并保存在一个全局变量中。然后改变用户界面,让客户能够输入城市的编码。
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;
客户现在要搜索旅行目的地城市中的旅馆。
用户输入城市编码并单击提交按钮,从而调用客户机 Javascript 中的 getRates() 函数。
getRates 函数使用 OTA_HotelAvailRQ XML 模式生成请求旅馆信息所需的应用服务器调用。
清单 6. 使用 OTA_HotelAvailRQ 模式创建的搜索旅馆消息
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 请求的形式发送到应用服务器。
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 调用
" <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 调用
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 属性的所有旅馆。
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 响应中的名称空间。
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 调用中使用了一个名称空间别名。
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;
在最后一步中,客户选择一家旅馆并预订房间。从数据库中保存的客户个人信息中获得信用卡信息。
客户现在可以在列表中选择一家旅馆,从而在这家旅馆预订房间。
当客户单击 Select 时,调用客户机 Javascript 中的 bookroom 函数。使用 AJAX API 将一个 XML 消息(请求预订房间)以 POST 请求的形式发送到应用服务器。这个请求包含旅馆 ID、客户名、要预订的房间数量和客户信用卡需要支付的数额。
清单 13. 客户机向应用服务器发送 REST 调用来预定房间
... {
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 表的一个加密列中。尽管在这个示例中密码是硬编码的,但是在真实的场景中密码可能是客户用来登录的密码。
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.zip | 42 KB | HTTP |
关于下载方法的信息 |
学习
- “ISV success with DB2 Viper”:准备将应用程序、例程和脚本迁移到 DB2 Viper。
- 关于 DB2 XML 的技术文章:寻找更多关于 DB2 和 XML 的文章。
- PayPal API Reference:了解关于 PayPal API 的信息。
- Open Travel Alliance:了解关于 Open Travel Alliance 的更多信息。
- “XML Programming with PHP and Ajax”:在面向服务体系结构和其他业务场景中使用 DB2 9 的 XML 功能。
- “Native XML Support in DB2 Universal Database”:对新的 DB2 XML 支持和传统关系数据库技术进行对比。
- developerWorks Information Management 专区:学习关于 DB2 的更多知识。在这里可以找到技术文档、how-to 文章、培训、下载、产品信息等等。
- 随时关注 developerWorks 技术活动和网络广播。
接下来,应用服务器创建一个 SOAP 调用,调用在部门内部防火墙后面运行 article4 数据库中的 bookaroom 存储过程。旅馆 ID 和客户个人信息作为参数传递给这个存储过程。
清单 15. 应用服务器对 bookaroom 存储过程执行一个 SOAP 调用
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 房间预订消息。还需要添加另一个表来处理预订信息。
out invoice varchar ( 64 ))
language SQL
begin
set invoice = ' INV001 ' ;
return 1 ;
end
使用 DOM 包装器类将 SOAP 响应字符串解析为 DOM 对象,并使用 XPath 调用提取出发票号。请查看 db2soapdriver.java,了解 DB2 存储过程调用产生的 SOAP 响应的模式。
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 调用的基本步骤如下:
- 为特定的 API 方法构造一个请求参数字符串。
- 通过 HTTPS 连接向 PayPal 服务器发送这个参数字符串。
- 处理服务器响应中的 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 数据模型是这个环境中最自然的编程模型。