本文标签:数据库 DB2 9
简介 PHP 支持简单的 Web 应用程序开发和部署环境 。这是它得到普遍应用的原因之一 。DB2 9的原生 XML 功能进一步简化了开发过程 。这种简化体现在以下方面:
- 应用程序代码较少,复杂性降低
- 较简单的关系模式
- 更好地管理因为更改业务需要而发生的模式演化
| 在本文中,我们将在该基础上进行构建并说明使用 DB2 原生 XML 功能简化应用程序代码和关系模式的有效性 。还将讲述业务需求更改对数据的影响(模式演化)以及对应用程序代码和关系模式的影响 。 为了说明我们的推理,将通过一个模拟在线商店的使用情景来进行说明,该商店向注册客户出售古董银器 。 我们将在该情景中说明的一些要点包括:
设置 PHP 环境的容易程度将 DB2 原生 XML 功能与 PHP 应用程序(包括用 PHP 和 XQuery 编写的 Web 服务)集成的容易程度使用 XQuery、存储过程和视图将业务逻辑和数据转换放到数据库中 。 | 在该情景中使用的 DB2 XML 功能将包含以下方面:
按分列结构在列中存储 XML 文档使用 XQuery 进行搜索和发布在 DB2 存储过程和视图中支持 XML使用 XML 索引提高性能 |
为突出使用 DB2 原生 XML 支持对 PHP 应用程序代码和关系模式设计的影响,该情景将创建一个并行环境,该环境使用不包含任何 XML 功能的数据库(例如,MySQL) 。我们将研究这两个环境在应用程序代码、数据库查询和关系模式方面的差异 。还将说明选择特定代码、模式或查询以及备选方案(如果可能)的理由 。 情景 该情景模拟向注册客户出售古董银器的在线商店 。因为该情景的一个目的是说明不同的数据库环境以及它们对应用程序代码的影响,所以我们将对两个应用程序进行同时说明,一个应用程序使用 DB2 原生 XML,另一个应用程序使用类似 MySQL 的开放源码 RDMS,具有有限的 XML 功能或没有任何 XML 功能 。 因此,访问 Web 站点的客户将看到一个包含两个垂直面板的页面 。每个面板都将显示同一应用程序的一个版本,提供相同的用户体验,但在后端使用不同的数据库:
具有原生 XML 支持的 DB2其他 RDBMS(在本例中为不使用任何 XML 功能的 DB2) |
为显示应用程序代码的差异,每个面板进一步分为两个水平框架,上面的框架显示在线商店,下面部分显示代码段 。当用户进行任何操作(如单击某一种类或产品图像)时,上面部分都会生成一个新页面 。下面部分显示创建此页面所需的代码 。 图 1. 示例应用程序面板
这将说明,虽然在任何一个应用程序中用户的体验没有变化,但代码复杂性发生了很大变化 。这种对比将突出用 PHP 编写的普通 SMB 应用程序使用 DB2 原生 XML 功能的好处 。 注意:我们对此情景的假设是业务数据已经是 XML 格式的,尽管数据库可能没有任何 XML 功能 。这将产生可用于数据库的使用 XML 功能的 PHP 应用程序代码(如简单 DOM) 。具有有限 XML 功能或无 XML 功能的数据库将 XML 数据存储为 CLOB/BLOB 数据类型,或分割到关系字段中 。 在 Web 站点中浏览时的功能和用户体验 Web 站点将为用户提供索引,列出商店中所有可用银器的种类和品牌 。用户单击某一种类或品牌时,将显示该种类或品牌的货品列表 。选择列表中的任何货品都会在页面中显示该货品的详细信息 。用户可以将这些货品添加到购物车中 。一旦用户提交了订单,将会创建采购订单并根据此采购订单向用户提供发票 。用户可以随时检查购物车中的货品 。用户还能够得到他们过去已订购的所有货品的报告 。 应用程序体系结构 图 2 显示了示例应用程序的基本体系结构 。 图 2. 应用程序体系结构
关系和 XML 模式 XML 文档和模式 原生 XML 存储不需要 XML 列与特定 XML 模式关联 。需要对插入到数据库中的 XML 文档进行的任何验证都在插入语句中使用 SQL/XML 函数显式地进行 。附录中包含 XML 文档示例 。 关系模式 对于这两个数据库,用于存储这些 XML 文档的关系模式将有所不同 。 对于 DB2 原生 XML,将有三个表,每个表包含两列 。 图 3. DB2 原生 XML 模式
对于无 XML 支持的 RDBMS,将有四个表,每个表包含多个列: 图 4. 无 XML 支持的情景的关系模式
可以看出,与无 XML 支持的 RDBMS 相比,DB2 原生 XML 的关系模式非常简单 。 我们已经通过将采购订单文档存储为 BLOB,在基本关系数据库中尽量保持采购订单表模式简单 。当查看生成订单历史记录时,这种操作的作用将非常明显 。 DB2 PHP 驱动程序 在开始讲述 PHP 应用程序代码之前,我们先了解一下 PHP 的 DB2 驱动程序 。ibm_db2 驱动程序支持两种连接数据库的方法:编目 和非编目 。编目连接可以是本地数据库(如果有 DB2 服务器在本地运行),也可以是远程 DB2 服务器节点 。第二种方法通常用于远程非编目连接,需要构建连接字符串(类似于 JDBC URL)以建立非编目连接 。以下代码连接编目数据库 。(客户机应用程序不需要知道或关注编目连接时本地的还是远程的 。)
$conn = db2_connect($dbname, $dbuser, $dbpass); if(!$conn) { echo db2_conn_errormsg(); die("Unable to connect to database!"); } | 还可以使用 db2_pconnect 创建与数据库的持久连接 。调用 db2_close 时,持久连接实际将不被关闭,因为连接句柄将在请求中保留 。有关 PHP 的 IBM DB2 驱动程序的详细信息,请访问 http://www.php.net/manual/en/ref.ibm-db2.php 。在下面的代码段中,假设 $conn 是有效连接句柄 。 填充数据库 在 Web 站点可以开通之前,需要使用客户信息和产品目录填充数据库 。对于我们的情景,将不详细说明如何获取此数据 。假设其以 XML 文档的形式包含在本地文件系统的文件中 。下面显示了连接数据库和执行 SQL 插入语句所需的 PHP 代码段示例 。 DB2 Viper 因为每个产品文档都包含产品 ID 属性,我们需要使用 PHP 的 SimpleXml API 提取该 ID 。 注意: 使用此 API 比操作 DOM 对象容易得多,该对象在 PHP 版本 5 之前是惟一选择 。
- 创建数据库连接:
$conn =db2_connect($dbname, $dbuser, $dbpass); |
- 从文件打开文档,成为一个变量:
$fileContents = file_get_contents("products/p1.xml"); |
- 从此变量创建简单的 XML 对象:
$dom = simplexml_load_string($fileContents); |
- 从文档中提取产品 ID:
$prodID = (string) $dom["pid"]; |
- 创建准备好的语句,将 XML 文档插入数据库中:
$stmt =db2_prepare($conn, "INSERT INTO xmlproduct VALUES (?, ?)"); |
- 将从文档中提取的产品 ID 作为参数与文档一起传递到查询:
db2_execute($stmt, array($prodID, $fileContents); |
注意插入数据到 XML 列中与插入数据到任何 CLOB 列中没有区别 。因为这个 DB2 新版本允许在插入时对 XML 数据进行隐式分析,我们不需要对传入值显式地调用 XMLPARSE 。如果我们希望在 XML 标记周围保留无关空格,则可以使用带 RESERVE WHITESPACE 选项的 XMLPARSE 函数 。 注意: 这些代码段中的查询都使用斜体表示,以区别于 PHP 应用程序代码 。 非 XML RDBMS 因为此数据库不具有任何 XML 功能,产品文档需要分解到两个关系表中 。关系模式和 XML 模式之间的映射信息将直接嵌入 PHP 应用程序代码中 。
- 首先加载文档到 DOM 中:
$fileContents = file_get_contents("$products/p1.xml"); $dom = simplexml_load_string($fileContents); |
- 现在将产品的单个元素分割到本地变量中:
$prodID = (string) $dom["pid"]; $prodName = (string) $dom->description->name; $prodDetails = (string) $dom->description->details; $prodPrice = (float) $dom->description->price; |
- 每个产品的图像 URL 需要存储在单独的图像表中:
$images = array(); foreach($dom->description->images->image as $image) { switch((string) $image[type]) { case thumbnail:$prodImgThumb = (string) $image; $prodImgAlias = (string) $image[alias]; if(!$prodImgAlias) $prodImgAlias = NULL; $stmt = db2_prepare($conn, "INSERT INTO sqlimages (Pid, Type, Alias, Location) VALUES (?, ?, ?, ?)"); db2_execute($stmt, array($prodID, thumbnail, $prodImgAlias, $prodImgThumb)); case full: $prodImgFull = (string) $image; $prodImgAlias = (string) $image[alias]; if(!$prodImgAlias) $prodImgAlias = NULL; $stmt = db2_prepare($conn, "INSERT INTO sqlimages (Pid, Type, Alias, Location) VALUES (?, ?, ?, ?)"); db2_execute($stmt, array($prodID, full, $prodImgAlias, $prodImgFull)); } } |
- 当前实现 ibm_db2 驱动程序不能适当地将 NULL 变量作为参数来处理,以便执行函数;因此我们使用一个非强制性解决方案:
if(!$prodBrand) $prodBrand = " "; if(!$prodCategory) $prodCategory = " "; if(!$prodImgFull) $prodImgFull = " "; |
- 现在保存产品表中的产品信息:
$stmt = db2_prepare($conn, "INSERT INTO sqlproduct (Pid, Name, Details, Brand, Category, Price, Weight, Size, Description) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"); db2_execute($stmt, array($prodID, $prodName, $prodDetails, $prodBrand, $prodCategory, $prodPrice, $prodWeight, $prodSize, $fileContents)); |
创建主页 主页包含在线商店中所有可用产品的种类和品牌的索引 。索引的右边区域显示所有货品的列表 。 图 5. 主页
创建种类和品牌的索引列表 索引通过查询数据库中所有产品的惟一种类和品牌的列表而创建 。启动应用程序时将创建此列表 。 DB2 Viper
- 首先创建 DB2 视图,以使用 XQuery 列出种类,XQuery 在所有产品中循环并返回所有惟一种类:
CREATE VIEW Categories(Category) AS SELECT DISTINCT(XMLCAST( XMLQUERY(for $i in $t/product/description/category return $i PASSING BY REF T.DESCRIPTION AS "t" RETURNING SEQUENCE) AS VARCHAR(128))) FROM xmlproduct AS t |
- 现在从应用程序调用该视图:
$stmt = db2_exec($conn, "SELECT * FROM Categories"); while(list($cat) = db2_fetch_array($stmt)) { echo "$cat "; } |
非 XML RDBMS 从产品表创建种类的惟一列表:
$stmt = db2_exec($conn, "SELECT DISTINCT(category) FROM SQLPRODUCT"); while(list($cat) = db2_fetch_array($stmt)) { echo "$cat ";} | 这两种情况中的应用程序代码相似 。创建 XML 数据视图使我们可以轻松地查询视图,从而有助于从应用程序代码理解产品 XML 的结构 。需要更改视图中的 XQuery 以查找品牌元素,同样 SQL 调用也需要查看 Brand 列 。 模式演化对索引列表的影响 根据客户反馈,我们需要允许用户浏览站点,以查找镀银的货品或由纯银制造的货品 。我们看一下向索引中添加子种类对以下各项的影响:XML 模式、关系模式、查询和 PHP 应用程序代码 。 对 XML 模式和文档实例的影响 向产品 XML 模式中的种类元素添加新属性(catx) 。产品的所有新 XML 文档现在都用纯银或镀银适当地填充了此属性: Miscellaneous 对关系模式的影响
- DB2 Viper
这将不需要对关系模式进行任何更改,因为 XML 文档存储在单个列中 。
- 非 XML RDBMS
在基本关系数据库中,将需要更改产品表的模式,添加名为 catx 的另一列 。这可能涉及删除并重新插入所有产品文档 。 对查询的影响
- DB2 Viper 创建索引所需的 XQuery 将发生变化以在条件中包含这个新属性 。同样,用于根据索引中的选择列出货品 XQuery 也将发生变化来包括新条件 。
- 非 XML RDBMS
插入语句将发生变化以包括新列 。 创建索引所需的查询将发生变化以在 WHERE 子句中包括这个新列 。同样,用于根据索引中的选择列出货品的查询也将发生变化以包括新条件 。 对应用程序代码的影响
- DB2 Viper
应用程序代码将没有任何更改 。
- 非 XML RDBMS
- 将需要额外的 DOM 代码,以分割出子种类信息 。
- INSERT 语句将需要额外的参数 。
- 所有数据都有可能需要重新插入,这导致终端用户有一段时间无法操作 。
用户单击种类或品牌时列出货品 用户单击特定种类或品牌时,将生成该种类或品牌中所有货品的列表 。列表中的每个货品都有简短描述和到缩略图像的 URL 。此列表显示在主页中并在其中进行格式设置 。 图 6. 某一种类中的货品列表
DB2 Viper 在 DB2 中,XQuery 不仅创建列表,而且还将其转换为 HTML 输出,从而浏览器可以直接使用 。使用 XQuery 的此功能,不仅可以推出业务逻辑,而且可以发布到数据库服务器,从而有效地使中间层应用程序非常简单 。这正是使用 PHP 而不使用 Java™ 或 VS .NET® 的原因 。
$xquery =for $i in $t/product let $thumb := $i/description/images/image[@type="thumbnail"] where $i/description/category = " . htmlentities($category) . " return
 {$i/description/name}
; $stmt = db2_prepare($conn, "SELECT XMLSERIALIZE(XMLQUERY( $xquery PASSING BY REF T.DESCRIPTION AS \"t\" RETURNING SEQUENCE) AS CLOB(32K)) FROM xmlproduct AS t"); db2_execute($stmt); while(list($product) = db2_fetch_array($stmt)){echo $product;} | 查看上面的应用程序代码,我们注意到 PHP 代码减少为两行,而数据库查询包含大部分逻辑 。 注意: 我们需要对使用 HTML 实体的任何传入 CGI 变量(如种类)进行转义,从而它们在插入 XQuery 时不包含任何非法的非转义实体 。 虽然 XQuery 可以进行发布转换,但在许多情况下可能不适合进行此操作 。由于设计、性能和样式原因,可能更适合于在中间层或客户机中使用 XSLT 转换来创建最终的 Web 页 。在许多情况中,将两个查询捆绑为一个查询会更方便 。使用 XQuery 并不阻止开发人员使用 XSLT 进行转换 。我们在此的尝试将显示 XQuery 不仅可以进行数据搜索,而且可以对数据运行复杂业务逻辑和转换 。 非 XML RDBMS 首先查询数据库以获取与所选种类匹配的所有货品的行集:
$sql = "SELECT P.Pid, P.Name, I.Location FROM sqlproduct P, sqlimages I WHERE P.Category = ? AND I.Pid = P.Pid AND I.Type = ?"); $stmt = db2_prepare($conn, $sql); db2_execute($stmt, array($category, "thumbnail")); | 然后在结果集中循环并使用 PHP 代码创建 Web 页:
while(list($prodPid, $prodName, $prodImg) = db2_fetch_array($stmt)) { ?>
">  "> } ?> | 虽然发布代码与 XQuery 中的非常相似,一个显著区别就是此代码在中间层中运行和维护,而不是在数据库服务器中 。所以如果 XQuery 在数据库服务器中注册为存储过程,其维护将作为数据库的一部分 。我们将在下一步中进行此操作 。 产品详细信息 用户单击商店列出的任何产品时,应用程序都会创建一个详细产品页面,其中包含该产品的说明、尺寸、价格和可能的附加图像等信息 。 图 7. 产品详细信息
DB2 Viper 创建产品详细信息的 XQuery 保存为存储过程 。 用于创建产品详细信息的存储过程 用 SQL/PL 编写 getProduct 存储过程,其接受产品 ID 作为参数,并返回记录集的游标 。这个存储过程主要执行 XQuery,其在页面一侧生成显示产品详细信息及其图片以及其他缩略图的版面 。
CREATE PROCEDURE getProduct(IN id VARCHAR(10)) DYNAMIC RESULT SETS 1 LANGUAGE SQL BEGIN BEGIN DECLARE c_cur CURSOR WITH RETURN FOR SELECT XMLSERIALIZE(XMLQUERY(for $i in $t/product let $thumb := $i/description/images/image[@type="thumbnail"] let $name := $i/description/name/text() let $details := $i/description/details/text() let $price := $i/description/price let $size := $i/description/size return
{$name}{ for $j in $i/description/images/image[@type != "thumbnail"][1] return ![]() src="data/images/{$thumb}.jpg" width="200"/> } Details: {$details} Price: ${$price/text()} Size: {$size/text()} {$size/@units/text()} >Click here to Buy
{ for $j in $i/description/images/image[@type != "thumbnail"] [position() != 1] return
}
PASSING T.DESCRIPTION AS "t" RETURNING SEQUENCE) AS CLOB(32K)) FROM xmlproduct T WHERE Pid = id; OPEN c_cur; END; END | 注意: XQuery 中的与号需要进行转义 。这适用于所有特殊字符 。 重点: 因为 XQuery 处理 XML 类型,所以所有返回的数据都将对特殊字符进行转义 。解决此问题的一种方法是使用转型函数将查询的返回数据转型为 VARCHAR 。 应用程序代码
$stmt = db2_prepare($conn, "CALL getProduct(?)"); db2_execute($stmt, array($pid)); list($product) = db2_fetch_array($stmt); echo $product; | 应用程序代码现在简化为对存储过程和将结果输出到浏览器的任务的简单调用 。现在大多数应用程序代码都在数据库服务器中维护和运行 。发现的另一个事实是,从存储过程查询数据比直接从代码执行查询简单,至少与其一样简单 。 非 XML RDBMS 虽然可以在关系情况下创建存储过程,但对于这种情况可能会复杂得多 。
- 从产品表获取产品详细信息:
$stmt = db2_prepare($conn, "SELECT P.Name, P.Details, P.Price, P.Size, I.Location, I.Alias FROM sqlproduct P, sqlimages I WHERE P.Pid = ? AND P.Pid = I.Pid AND I.Type = ? FETCH FIRST ROW ONLY"); db2_execute($stmt, array($pid, full)); list($prodName, $prodDetails, $prodPrice, $prodSize, $prodImgThumb, $prodImgAlias) = db2_fetch_array($stmt); |
- 现在通过在数据周围放置 HTML 标记创建显示:
通过使用 构造,可以在这里混合 PHP 和 HTML 代码:
Details: Price: $ Size:
">Click here to Buy
|
- 查询与此产品关联的图像并将其 URL 添加到输出 Web 页中:
$stmt = db2_prepare($conn, "SELECT DISTINCT(Location) FROM sqlimages WHERE Pid = ? AND Type = ? AND NOT Location = ?"); db2_execute($stmt, array($pid, full, $prodImgThumb)); while(list($prodImg) = db2_fetch_array($stmt)) { ?>
} ?>
|
因为产品数据已经分割到两个表中,且 PHP 和 HTML 代码已经混合,我们需要进行两个单独查询来获取产品详细信息和图像位置 。 购物车 购物车是任何在线商店不可缺少的一部分 。因此,它是一个适合创建为 Web 服务的实用程序 。可定制此 Web 服务来计算价格、税款、币种换算、运输成本等等 。在我们的实现示例中,已经使用 PHP 和 XQuery 创建了 Web 服务,可以进行价格和税款计算 。 客户添加到购物车中的货品保存为客户端 cookie 。此 cookie 在 PHP 中以关联数组形式读入并指定给变量 $cart 。 图 8. 购物车
DB2 Viper Web 服务提供商和查询
- 同样,我们已经使用 XQuery 将输出版面和业务逻辑嵌入到一个查询中 。除了购物车信息,Web 服务还接受销售税率参数,购物车信息为一个包含产品 ID 及各自数量的 XML 文档 。这使我们可以将购物车信息作为 XML 值传递到 XQuery 中 。
function getCart($cart, $taxrate) { global $conn; $result = ""; $xquery = for $dummy in (1) |
- 在 XQuery 中,与其他任何原生 XML 文档一样,变量 $cart 可以进行迭代:
let $items := for $i in $cart/items/item let $product := db2-fn:xmlcolumn("XMLPRODUCT.DESCRIPTION") /product[@pid = $i/@pid]/description let $name := $product/name/text() let $price := $product/price/text() let $itemPrice := if($price = 0 or empty($price)) then ("$0.00") else (concat("$", $price)) return
|
|
- 对于每个货品,使用从购物车信息传递的数量和从数据库中的产品目录信息获取的价格,计算该货品的总价:
{$price * $i/@quantity}
| {$i/@pid}">{$name}
|
{xs:integer($i/@quantity)} {$i/@pid}">Remove
|
{$itemPrice}
|
return
- 返回 XML(XHTML)结构,包含每个货品的总价计算以及发布信息:
- 从返回的结构计算购物车中所有货品的总价 。我们实际上将查询的一部分的输出作为该查询的另一部分的输入变量 。
Total all ${ sum( $items/noframes/text() ) }
|
|
Tax ({$tax * 100}%) |
|
- 使用从客户机传递到 Web 服务的税变量计算付款 。
注意: 上面计算的所有货品的总价可以使用 let 语句分配给一个变量 。然后此变量可以用于计算税和总付款 。
${ xs:decimal(sum($items/noframes/text())) * $tax }
|
|
Grand Total< /strong> |
${ xs:decimal(sum($items/noframes/text())) * (1 + $tax) } < /strong>
| = =; 我们留给读者一个练习,以更好的形式重写循环的最外层 。
- 虽然 XQuery 不支持运行时参数绑定,但是有一个解决方案 。运行时参数可通过使用 XMLQuery 函数的 PASSING BY 子句传递到 XQuery 中:
$stmt = db2_prepare($conn, "VALUES( XMLSERIALIZE( XMLQUERY($xquery PASSING BY REF CAST(? AS XML) AS \"cart\" , CAST(? AS DECIMAL (10,8)) AS \"tax\" RETURNING SEQUENCE) AS CLOB(32K)))"); | 注意,购物车信息以 XML 字符串的形式传递到查询中 。此字符串通过使用 CAST (? As XML) 函数转换为 XML 类型 。
- 如果查询注册为存储过程,则运行此 Web 服务所需的 PHP 代码将简化为以下几行:
if($stmt) { if(!db2_execute($stmt, array($cart, $taxrate))) { return db2_stmt_errormsg($stmt); } list($result) = db2_fetch_array($stmt); if(!$result) return db2_stmt_errormsg($stmt); } else { $result = db2_stmt_errormsg(); }return $result; } |
- 注册 Web 服务:
$server = new SoapServer(null, array(uri = > http://ibm.com/db2/xml/php)); $server->addFunction(getCart); $server->handle(); ?> |
正如可以从以上代码示例中看到的,使用 PHP 和 DB2 原生 XML 支持创建 Web 服务非常简单 。而且,由于服务简单,我们不需要定义 WSDL(Web Services Description Language,Web 服务描述语言)文档 。 Web 服务客户机 现在可以从客户机代码调用此 Web 服务,在 Web 站点中显示购物车信息 。实际购物车页面就是一个调用 Web 服务的客户机 。
- 创建购物车信息的 XML 字符串,它将作为参数传递到 Web 服务:
$cartXML = ""; foreach($cart as $cpid => $quantity) { $cartXML .= "- ";
} $cartXML .= " "; |
- 连接 Web 服务:
$client = new SoapClient(null, array(location => http://127.0.0.1/cartsvc.php, uri => http://ibm.com/db2/xml/php)); $taxrate = 0.0; |
- 调用 Web 服务并将返回的字符串输出到浏览器:
echo $client->getCart($cartXML, $taxrate); ?> |
注意: 因为购物车作为 Web 服务来实现,所以可以从任何语言调用它 。 非 XML RDBMS 此示例的关系版本没有 Web 服务或销售税计算 。虽然查询看起来比 XQuery 简单,但我们必须为购物车中的每个货品查询数据库,导致数据库流量增加,应用程序代码有些更面向循环 。由于可以使用 PHP 容易地创建 Web 服务,从关系部分创建 Web 服务将不会很难 。与在 XML 版本中一样,getCart 函数会接受购物车作为参数,其格式需要提前确定 。如果是 XML 值,则需要使用 DOM 来读取购物车,或者可以是关联数组,只需对以上代码进行很少的更改 。另一方面,如果希望创建使用关系数据库显示购物车的存储过程,需要将 HTML 内容与从数据库中检索的数据合并 。将需要大量工作以使表示代码不在存储过程中,而在应用程序中 。
foreach($cart as $pid => $quantity) { $stmt = db2_prepare($conn, "SELECT Name, Price FROM sqlproduct WHERE Pid = ?"); db2_execute($stmt, array($pid)); if($stmt) { list($prodName, $prodPrice) = db2_fetch_array($stmt); ?>
|
">
|
">Remove
|
$
|
} } ?> 采购订单 一旦用户选择付帐,购物车将发送到应用程序,并为采购的货品生成 XML 采购订单文档 。 用于创建采购订单的 XML 代码和关系代码大部分都相同,惟一的差别是用于查找产品当前价格的查询 。
$stmt = db2_prepare($conn, "VALUES (NEXT VALUE FOR POid)"); db2_execute($stmt); list($POid) = db2_fetch_array($stmt); foreach($cart as $pid => $quantity) { $xquery = $t/product/description/price/text(); $stmt = db2_prepare($conn, "SELECT XMLSERIALIZE(XMLQUERY($xquery PASSING BY REF T.DESCRIPTION AS \"t\" RETURNING SEQUENCE) AS VARCHAR(8)) FROM xmlproduct AS t WHERE Pid = ?"); db2_execute($stmt, array($pid)); list($price) = db2_fetch_array($stmt); | 虽然我们可以使用 DOM 创建采购订单,在本例中合并 XML 片段将更简单 。
$stmt = db2_prepare($conn, "INSERT INTO xmlporder (POid, POrder) VALUES (?, ?)"); db2_execute($stmt, array($POid, $PO)); | 采购订单(PO)在关系数据库中存储为 CLOB(而不分割) 。完整存储的好处是任何由于采购订单的变化(如运输等其他信息)而发生的模式演化都不会有什么影响 。将 PO 存储为 CLOB,则可以在应用程序代码中使用 DOM 来检索相关信息 。但是,关系存储的简单和模式演化的好处会被查询性能的降低所抵消,当我们尝试创建采购历史记录报告时会发现这一点 。 发票 发票在付帐时返回给客户 。该发票是通过查询刚刚创建的采购订单而生成的 。因为采购订单不包含详细产品信息,所以需要进行单独查询,以便从产品表中查找产品详细信息 。 图 9. 发票
DB2 Viper
- 同样,我们使用单个 XQuery 创建最终发票 。
$xquery = for $po in $t/purchaseOrder let $sum := for $item in $po/items/item return $item/@quantity * $item/@price let $items := for $item in $po/items/item |
- 在采购订单和产品表之间创建联接以获取产品详细信息 。
let $name := for $i in db2-fn:xmlcolumn("XMLPRODUCT.DESCRIPTION")/product where $i/@pid = $item/@pid return $i/description/name/text() return
|
{$name}
|
{xs:string($item/@quantity)}
|
${xs:string($item/@price)}
|
return
{$items} {$po/text()}
|
Total |
${sum($sum)} |
;
有趣的是,以上 XQuery 与用于显示购物车内容的 XQuery 相似:
$stmt = db2_prepare($conn, "SELECT XMLSERIALIZE(XMLQUERY($xquery PASSING BY REF T.PORDER AS \"t\" RETURNING SEQUENCE) AS CLOB(32K)) FROM xmlporder AS t WHERE POid = ?"); db2_execute($stmt, array($id)); list($po) = db2_fetch_array($stmt); echo $po; | 还请注意,PHP 应用程序代码量达到最少,因为大部分业务逻辑和转换位于查询中 。 非 XML RDBMS
-
$stmt = db2_prepare($conn, "SELECT POrder FROM sqlporder WHERE POid = ?"); db2_execute($stmt, array($id)); $sum = 0.0; while(list($po) = db2_fetch_array($stmt)) { |
- 因为采购订单存储为 CLOB,我们需要使用 DOM 访问每个产品及其数量和价格 。使用简单 DOM 从采购订单中提取数据:
$dom = simplexml_load_string($po); foreach($dom->items->item as $item) { $cpid = (string) $item[pid]; $price = (float) $item[price]; |
- 跟踪 HTML 表每一行中的总价:
$sum += $price * (integer) $item[quantity]; |
- 需要进行单独查询来查找每个货品的名称 。
$stmt2 = db2_prepare($conn, "SELECT Name FROM sqlproduct WHERE Pid = ?"); db2_execute($stmt2, array($cpid)); if($stmt2) { list($prodName) = db2_fetch_array($stmt2); ?>
|
|
|
$
|
} } }
代码的关系版本向应用程序引入了较多的逻辑 。 报告客户的订购历史记录 客户可以单击索引中的 Order history 链接,列出他们的所有采购订单: 图 10. 订购历史记录
DB2 Viper
- 使用 SQL/XML 返回按日期排序的客户采购订单 。对于每个采购订单,都将执行一个 XQuery,返回每个产品小计的格式化结果 。
$xquery = for $po in $t/purchaseOrder let $items := for $item in $po/items/item |
- 在 XQuery 内执行联接以显示每个产品的名称,因为采购订单仅存储产品 ID、价格和数量:
let $name := for $i in db2-fn:xmlcolumn("XMLPRODUCT.DESCRIPTION")/product where $i/@pid = $item/@pid return $i/description/name/text() return
{xs:string($item/@quantity)} x @ ${xs:string($item/@price)} {$name}
return
Order #{xs:string($po/@id)} placed on {xs:string($po/@orderDate)} {$items} ; $stmt = db2_prepare($conn, "SELECT XMLSERIALIZE(XMLQUERY ($xquery PASSING BY REF T.PORDER AS \"t\" RETURNING SEQUENCE) AS CLOB(32K)) FROM xmlporder AS t ORDER BY POid DESC"); db2_execute($stmt); while(list($po) = db2_fetch_array($stmt)) { echo $po; } |
非 XML RDBMS
- 在关系版本中,必须执行一个查询来获取每个采购订单中产品的列表:
$stmt = db2_prepare($conn, "SELECT POid, POrder FROM sqlporder ORDER BY POid DESC"); db2_execute($stmt); while(list($POid, $po) = db2_fetch_array($stmt)) { |
- 因为采购订单在关系数据库中存储为 CLOB,我们必须使用 DOM 访问订单日期和各个产品 。PHP 5 的 SimpleXML 功能在此会再次派上用场 。
$dom = simplexml_load_string($po); ?>
Order # placed on foreach($dom->items->item as $item) { |
- 对于每个产品,必须查询详细信息,在本例中查询其名称:
$stmt2 = db2_prepare($conn, "SELECT Name FROM sqlproduct WHERE Pid = ?"); db2_execute($stmt2, array( (string) $item[pid] )); while(list($prodName) = db2_fetch_array($stmt2)) { ?>
x @ $
} } } |
XML 索引 虽然我们没有创建任何关系索引,但将创建一些 XML 索引来说明如何使用 DB2 原生 XML 支持从文档中的任何元素或属性创建索引 。每个产品文档都有一个惟一产品 ID 以及浏览目录时常常使用的其他重要信息,包括种类、品牌和名称 。对于产品 XML 列,将创建这四个索引 。
CREATE UNIQUE INDEX prod_pid ON xmlproduct(description) GENERATE KEY USING XMLPATTERN /product/@pid AS SQL VARCHAR(10) CREATE INDEX prod_name ON xmlproduct(description) GENERATE KEY USING XMLPATTERN /product/description/name AS SQL VARCHAR(128) CREATE INDEX prod_category ON xmlproduct(description) GENERATE KEY USING XMLPATTERN /product/description/category AS SQL VARCHAR(128) CREATE INDEX prod_brand ON xmlproduct(description) GENERATE KEY USING XMLPATTERN /product/description/brand AS SQL VARCHAR(128) | 正如您可以看到的,可以从属性以及元素创建索引 。 设置运行应用程序的环境 设置 Apache 和 PHP 首先需要设置 PHP 开发环境和 Web 服务器来运行服务器端 PHP 脚本 。这些说明是围绕 Windows 的,但是代码也可以在 Linux 和其他类似 UNIX 的平台上运行,而不需要更改 。有关如何设置使用 Apache 来设置 PHP 模块的信息,请参考 PHP 文档 。
- 确保安装了 DB2 Version 8.2 或更高版本客户机库 。
- 从 http://httpd.apache.org/ 下载并安装 Apache 最新的 2.0 版 。对于开发工作,最好不要将 Apache 安装为服务,因为您会发现将频繁地重新启动它 。启动 Apache 并导航到 http://localhost/,以确保 Apache 正在运行 。
- 下载 PHP 5 的最新稳定版本的 zip 包,然后将其解压缩到 c:\php 。
- 从同一页面下载 PECL(PHP Extension Community Library,PHP 扩展公共库)模块集合 。将此 zip 文件解压缩到 c:\php\ext 。
- 将 c:\php\php.ini-dist 复制到 c:\php\php.ini,然后用编辑器将其打开 。
-
- 查找 extension_dir 设置并将其更改为:
extension_dir = "c:/php/ext/"
- 查找 Dynamic Extensions 部分并添加下列行:
extension=php_ibm_db2.dll extension=php_soap.dll
- 用编辑器打开 Apache 的配置文件 httpd.conf 。
-
- 查找 Dynamic Shared Object (DSO) Support 部分,在 LoadModule 指令列表的后面添加下列行:
LoadModule php5_module "c:/php/php5apache2.dll" AddType application/x-httpd-php .php AddType application/x-httpd-php-source .phps PHPIniDir "c:/php"
- 查找 DirectoryIndex 指令,将 index.php 添加到列表中:
DirectoryIndex index.php index.html index.html.var
- 您可能希望删除 Apache 安装的 htdocs 目录(DocumentRoot 指令的值)中包含的文件 。在 htdocs 目录中创建一个新文件 info.php,内容为:
- 启动 Apache 并导航到 http://localhost/info.php 。如果向下滚动页面时看到 ibm_db2 配置,则已经成功配置了 Apache、PHP 和 ibm_db2 驱动程序!
设置应用程序代码 尝试运行附加的应用程序之前,确保具有包含 PHP 支持的 Web 环境 。应用程序的前端可以在 Mozilla Firefox 中正确显示;因此,当您使用其他浏览器时可能会看到难看的页面布局 。请按照以下简单步骤来设置和运行应用程序:
- 先下载本文结尾的 zip 文件,然后将其解压缩到 htdocs 目录(通常为 C:\Program Files\Apache Group\Apache\htdocs) 。进入 silvercastles 目录 。
- 在将要使用的数据库服务器中,创建名为 silver 的数据库 。为了使 XML 功能可以与 DB2 Viper 一起使用,必须明确指定 Unicode 数据库:
CREATE DATABASE silver USING CODESET utf-8 TERRITORY us
- 如果要创建编目数据库连接,必须在本地计算机中从 DB2 Command Window 内运行以下命令:
CATALOG TCPIP NODE myNode REMOTE serverAddr SERVER serverPort CATALOG DB silver AT NODE myNode 确保用数据库服务器的主机名或 IP 地址及其 TCP/IP 端口(SVCENAME 数据库管理器配置变量)分别替换 serverAddr 和 serverPort 。如果未在服务器中设置 SVCENAME 变量,需要将其设置为空闲端口,然后重新启动 DB2: $ db2 UPDATE DBM CFG USING SVCENAME 12345 $ db2stop ; db2start
- 编辑 config.php 并修改适当的值 。如果需要,一定要对某些字符进行转义,因为您在修改实际的 PHP 代码 。
-
- $basedir:silvercastles 目录的路径(使用正斜线,以避免对路径进行转义,如 C:/Program Files/Apache Group/Apache/htdocs/silvercastles)
- $dbname:数据库名称
- $dbuser:允许连接数据库的用户名称
- $dbpass:用户密码
如果使用编目数据库连接,则执行到步骤 3 。另外,还需要修改以下值:
-
- $dbhost:数据库服务器的主机名或 IP 地址
- $dbport:服务器的端口号
- 保存配置并将浏览器指向 http://localhost/setup.php,以创建 XML 和关系表并插入数据 。单击 Continue 链接访问在线商店 。
(责任编辑:城尘 68476636-8003)
|