您可能尚未看到从 Oracle 数据库获得动态生成的 XML 数据并使用 Google Maps API 将其与 Web 页混搭在一起是多么容易。例如,如果您的 Oracle 数据库填充了与地理相关的有趣数据,则可以非常轻松地将该数据与 Google 的 JavaScript Maps API 集成在一起。
在本文中,我将演示如何使用 Oracle 的 XML DB 特性和 Java 构建一个混搭应用程序,以便将 Oracle 数据库中的数据与 Google Maps API 集成(使用的是 Oracle JDeveloper 10g;从这里获得示例项目文件)。
全球 ACME 宾馆定位应用程序简介
为了让您了解能够将 Oracle 数据库的功能与 Google Maps API 相结合的可能应用程序类型,下面提供了一个简单的应用程序,通过该应用程序,您可以查询全球的宾馆位置并在 Google Map 上查看结果。当您首次访问应用程序的 Web 页时,将看到 Google Map 并且顶部有一个简单的表单,可以输入查询参数。
图 1:ACME 全球宾馆定位程序
正如您在图 1 中看到的那样,指定的查询条件是“Beach access”、“under 400$”以及在“Americas”区域内,并单击了“Find Hotels”按钮。这返回了一组 Miami、Acapulco 和 Rio de Janeiro 的宾馆结果。单击图中的标记将在一个弹出窗口中显示宾馆的详细信息。您还可以看到,宾馆级别是 4 星,并且平均价格约为 293 美元。还显示了一个“Book it!”链接,从而允许您轻松转到该宾馆的 Web 站点以便预订房间。
正如您在本例中看到的那样,只需切换到 Google Maps 窗口上的“Map”选项,即可确切了解宾馆位于城市的哪个地方。其他查询可以生成许多您感兴趣的全球宾馆组合。
图 4:ACME 宾馆定位应用程序生成的各种结果
该应用程序的“酷”因素之一是,它具有极高的易用性,这是大部分 Google Maps 应用程序所具有的共同点。
构建 ACME 宾馆定位应用程序
在深入该应用程序背后的代码之前,我们先快速了解一下体系结构。正如您在图 5 中看到的那样,应用程序的体系结构包括一个在应用程序背后提供实际数据的 Oracle 数据库,以及一个运行 Java servlet 的应用服务器,该 servlet 可以使用 Oracle XML DB 与数据库通信并以 XML 格式检索数据。
图 5:ACME 宾馆应用程序的体系结构
实际的 Java servlet (XmlServlet) 利用一个特殊的类 XmlGenerator,该类可以使用 JDBC 发出查询并检索宾馆数据的 XML 流。该数据随后会返回到 servlet,而 servlet 又会将该数据发送到 html Web 客户端 (acme_hotels.html),然后客户端将使用 Google Maps API 显示数据。实际的 Map 图像或图块专门由 Google 提供。简单来说,这就是实质上的体系结构。下面,让我们详细研究一下代码。
REM Create a Geocoded Hotels Table
CREATE TABLE HOTELS (
ID NUMBER NOT NULL,
NAME VARCHAR2(500) NOT NULL,
DESCRIPTION VARCHAR2(500),
THUMB_IMG_URL VARCHAR2(500),
WEB_ADDRESS VARCHAR2(500),
ADDRESS VARCHAR2(1000),
CITY VARCHAR2(500),
POSTAL_CODE VARCHAR2(500),
STATE VARCHAR2(500),
COUNTRY VARCHAR2(500),
REGION VARCHAR2(100),
STARS NUMBER,
BEACH VARCHAR2(1),
AVE_PRICE_USD DOUBLE PRECISION,
POOL VARCHAR2(1),
LATITUDE DOUBLE PRECISION,
LONGITUDE DOUBLE PRECISION );
ALTER TABLE HOTELS
ADD CONSTRAINT HOTELS_PK PRIMARY KEY ( ID ) ENABLE;
除了明显的列(例如,name、description 等),请注意 latitude 和 longitude 列。这就是每个宾馆的确切地理位置,该地理位置将被提供给 Google Maps API 以便在地图上定位宾馆。
为了检索 hotels 表中的数据,ACME 应用程序使用在 Oracle Containers for Java (OC4J)(即 Oracle 应用服务器中的企业 Java 引擎)中运行的 Java servlet。Java servlet (XmlServlet) 本身只是充当 Web 客户端与查询数据库的代码之间的中间媒介。当最终用户单击“Find Hotels”按钮时,程序将对以 XML 格式从数据库请求宾馆地理数据的 servlet 进行一个 Ajax 调用。
它的工作方式是怎样的?servlet 先处理从 Web 客户端发送的查询参数,然后将其传送到另一个 Java 类 XmlGenerator,该类的作用是使用 JDBC 连接到数据库,并根据 servlet 发送的查询返回 XML 数据流。程序将使用 Oracle XML DB 自动生成 XML 内容。
下面让我们来详细了解该应用程序的体系结构,您可以在该体系结构中看到 Web 客户端如何与 XmlServlet 通信,而后者又如何使用 XmlGenerator 类以 XML 格式提取宾馆数据。
String strqry =
"select name, description, thumb_img_url, web_address, address, city,
postal_code, state, country, region" +
"stars, beach, ave_price_usd, pool, latitude, longitude from " + tabName + wc;
OracleXMLQuery qry = new OracleXMLQuery(conn, strqry);
// Structure the generated XML document
qry.setRowIdAttrName(null);
qry.setMaxRows(50);
qry.setRowsetTag("hotels"); // set the root document tag
qry.setRowTag("hotelinfo"); // sets the row separator tag
// Get the XML document in string format
String xmlString = qry.getXMLString();
setXmlResponse(xmlString); }
public void setXmlResponse(String xmlResponse) {
this.xmlResponse = xmlResponse;
}
public String getXmlResponse() {
return xmlResponse;
}
}
XmlGenerator 类中的代码是可以使用 XML DB 选项的相当标准的 JDBC 代码。注意,除了标准的 JDBC 包,还导入了 XML DB 包 oracle.xml.sql.query.OracleXMLQuery。该类遵循标准的 Java Bean 方法,并具有一个属性:XmlResponse,其 getter 和 setter 方法位于类的末尾。顺便说一下,该属性将包含从数据库检索的 XML 响应。设置 JDBC 连接之后,代码的关键部分是:
OracleXMLQuery qry = new OracleXMLQuery(conn, strqry);
这将创建一个新的 OracleXMLQuery 对象,其连接和查询字符串已经指定。创建完成后,可以应用其他几个设置,包括 RowsetTag,该设置可指定“hotels”作为 XML 数据的根元素。RowTag 设置为“hotelinfo”,并充当每个返回记录的重复元素。得到的 XML 数据格式如下:
<?xml version = '1.0'?>
<hotels>
<hotelinfo>
<NAME>ACME Luxury Acapulco</NAME>
<DESCRIPTION>The ACME Luxury Acapulco is a luxurious beautiful
beach front with all the amenities that a 4 star hotel
is expected to have.</DESCRIPTION>
<THUMB_IMG_URL>images/acapulco-sm.jpg</THUMB_IMG_URL>
<WEB_ADDRESS>http://acmeluxuryhotelacapulco-bogus.com</WEB_ADDRESS>
<ADDRESS>2322 La Sienna</ADDRESS>
<CITY>Acapulco</CITY>
<POSTAL_CODE>38432</POSTAL_CODE>
<COUNTRY>MX</COUNTRY>
<STARS>4</STARS>
<BEACH>Y</BEACH>
<AVE_PRICE_USD>375.95</AVE_PRICE_USD>
<POOL>Y</POOL>
<LATITUDE>16.850548</LATITUDE>
<LONGITUDE>-99.920654</LONGITUDE>
</hotelinfo>
<hotelinfo>
<NAME>ACME Luxury San Francisco</NAME>
<DESCRIPTION>...</DESCRIPTION>
...
</hotelinfo>
<hotelinfo>
...
</hotelinfo>
...
</hotels>
现在,我们已经探究了 Java servlet 及其 XML 生成代码,下面来看一下 HTML Web 客户端,该客户端的职责是显示 Google Map、处理表单值以及发出对 servlet 的 Ajax 请求。
HTML Web 客户端:acme_hotels.html
回想一下,客户端 Web 页实际上只包含一个 HTML 表单和一个 Google Map。表单包含复选框、一个下拉列表和一个提交按钮。Google Map 以中性状态启动,并显示全球视图。在探究表单代码之前,我们先探究一下呈现最初的 Google Map 的少量 JavaScript 代码。
// Initialize map - called when page is loaded function initMap(){ if (GBrowserIsCompatible()) { map = new GMap2(document.getElementById("map"));
map.setCenter(new GLatLng(2, -55), 1); map.enableScrollWheelZoom(); map.setMapType(G_HYBRID_TYPE); map.addControl(new GLargeMapControl()); map.addControl(new GMapTypeControl()); } }
function showHotels(){
clearMap();
loadHotelLocations();
}
clearMap( ) 函数使用 Google Maps API 调用 map.clearOverlays( ) 来清除任何现有的地图叠加。您很快就能了解到,Google Maps 叠加 是您在地图顶端添加额外可视内容的方式,例如,绘制一条直线或者添加一个弹出窗口,这实际上正是该应用程序所作的事情。
loadHotelLocations( ) 函数是应用程序的实际“劳力”。它组装在表单上指定的查询参数,然后使用 Google Maps API 调用 GDownloadUrl( ) 进行 Ajax 调用,以根据查询下载包含相应宾馆信息的 XML 流。下面是完整的函数:
function loadHotelLocations() {
var ajaxParms = generateRequestArgs();
// Make Ajax request to get Hotel data using Google Maps GDownloardUrl()
GDownloadUrl("/ACME-Hotel-Locator/xmlservlet" + ajaxParms, function(data, responseCode) {
var xml = GXml.parse(data);
var markers = xml.documentElement.getElementsByTagName("hotelinfo");
if (markers.length == 0){
alert("Your query returned no results.Please broaden your search criteria and try again.");
} else {
for (var i = 0; i < markers.length; i++) {
var point = new GLatLng(parseFloat(markers[i].getElementsByTagName("LATITUDE")[0].firstChild.nodeValue),
parseFloat(markers[i].getElementsByTagName("LONGITUDE")[0].firstChild.nodeValue));
var hotelName = markers[i].getElementsByTagName("NAME")[0].firstChild.nodeValue;
var hotelDescription = markers[i].getElementsByTagName("DESCRIPTION")[0].firstChild.nodeValue;
var hotelStars = markers[i].getElementsByTagName("STARS")[0].firstChild.nodeValue;
var hotelImg = markers[i].getElementsByTagName("THUMB_IMG_URL")[0].firstChild.nodeValue;
var hotelWebAddress = markers[i].getElementsByTagName("WEB_ADDRESS")[0].firstChild.nodeValue;
var hotelStars = markers[i].getElementsByTagName("STARS")[0].firstChild.nodeValue;
var avgPrice = markers[i].getElementsByTagName("AVE_PRICE_USD")[0].firstChild.nodeValue;
// Generate rating stars HTML DIV
var ratingHtml = generateRatingHtml(hotelStars);
point.name = "<div class='info-window'><b>" + hotelName + "</b><br/><table><tr><td><img src='" +
hotelImg + "' height='100'></td><td>" + ratingHtml + "<br/>Ave. Price:$"+ avgPrice +
" (USD)<br/> <a href='" + hotelWebAddress + "'>Book it!</a></td></tr><tr><td colspan='2'>" +
hotelDescription + "</td></tr></table><br/></div>";
mapMarkers.push(createHotelMarker(point));
}
showMap();
}
});
}
您在第二个参数/回调函数中可以看到,第一个参数是从查询返回的实际 XML 数据。在回调函数的主体中,我们调用了 GXml.parse(data) 以分析传入的 XML 数据,并将用作一个文档对象模型 (DOM) 对象,以便使用 JavaScript 来迭代值和管理数据。
var xml = GXml.parse(data); var markers = xml.documentElement.getElementsByTagName("hotelinfo");
在建立表示 XML 数据的 DOM 对象的句柄之后,将一个名为 markers 的变量指定给 getElementsByTagName( )(即,对应于父标记“hotelinfo”的元素数组)的返回值。如果从查询检索到数据,代码将迭代不同的元素,并在地图上为每个相应的宾馆添加点(标记)。以下是执行此操作的循环代码:
for (var i = 0; i < markers.length; i++) { var point = new GLatLng(parseFloat(markers[i].getElementsByTagName ("LATITUDE")[0].firstChild.nodeValue),
parseFloat(markers[i].getElementsByTagName("LONGITUDE")[0].firstChild.nodeValue));
var hotelName = markers[i].getElementsByTagName("NAME")[0].firstChild.nodeValue;
var hotelDescription = markers[i].getElementsByTagName("DESCRIPTION")[0].firstChild.nodeValue;
var hotelStars = markers[i].getElementsByTagName("STARS")[0].firstChild.nodeValue;
...
// Generate rating stars HTML DIV
var ratingHtml = generateRatingHtml(hotelStars);
point.name = "<div class='info-window'><b>" + hotelName + "</b><br/> <table><tr><td><img src='" + hotelImg + "' height='100'></td><td>" + ratingHtml + "<br/>Ave. Price:$"+ avgPrice + " (USD)<br/> <a href='" + hotelWebAddress + "'>Book it!</a> </td></tr><tr><td colspan='2'>" + hotelDescription + "</td></tr></table><br/></div>";
mapMarkers.push(createHotelMarker(point));
}
进一步检查之后,您将看到代码使用 Google Maps GLatLng 类构造函数(使用经度和纬度值作为参数)创建了一个新的 point 对象。这些值都是使用 DOM getElementsByTagName 调用从 markers 数组中提取的:
一旦创建了新的 GLatLng 点,剩余步骤就是将附加宾馆信息添加到点的 name 域。为此,需要使用同一个 DOM 方法从 XML 流中提取其他域(例如,name、description 等),然后在 HTML 中将它们一起连接到 name 域。您可能还注意到,ratingHtml 值是使用另一个函数 generateRatingHtml( ) 生成的,该函数用于提取 XML 流返回的星级信息,并生成 HTML 中包含星星图形的一小部分。
function generateRatingHtml(stars){
var starsHtml = "<div style='white-space:nowrap;'>";
for (i=0; i<5; i++){
if (i < stars )
starsHtml += "<img src='http://www.oracle.com/technology/pub/articles/images/star-rating-on.jpg'>";
else
starsHtml += "<img src='http://www.oracle.com/technology/pub/articles/images/star-rating-off.jpg'>";
}
starsHtml += "</div>";
return starsHtml;
}
function showMap(){ // Find boundary points of hotel location
var bounds = new GLatLngBounds();
for (var i=0;i < mapMarkers.length;i++) {
map.addOverlay(mapMarkers[i]);
bounds.extend(mapMarkers[i].getPoint());
}
// Reset center and zoom level based on queried hotel locations
map.setCenter(bounds.getCenter());
map.setZoom(map.getBoundsZoomLevel(bounds));
}
如您所见,构建该应用程序相对比较简单。两个 Java 类 XMLGenerator 和 XMLServlet 分别执行从 Web 页客户端响应 Ajax 请求、然后将其转换为数据库查询的任务。随后,使用 XMLServlet 通过 HTTP 将结果重新流入 XML 流,从而允许 Web 页中的 Google Maps 应用程序轻松分析 XML,并将其显示在地图上。就这样!有了 Oracle XML DB,生成 XML 数据非常轻松。在 Oracle JDeveloper 中创建 Java 类以及 HTML Web 页客户端也很轻松。
Chris Schalk 是 Google 开发提倡者,致力于推动 Google 的 Ajax API 和技术。在加入 Google 之前,Chris 是 Oracle Java 开发工具小组的首席产品经理和技术推广者。Chris 最近还与他人合著了《JavaServer Faces, The Complete Reference》(McGraw-Hill/Osborne) 一书。