PHP 支持简单的 Web 应用程序开发和部署环境。这是它得到普遍应用的原因之一。DB2 9的原生 XML 功能进一步简化了开发过程。这种简化体现在以下方面:
应用程序代码较少,复杂性降低较简单的关系模式更好地管理因为更改业务需要而发生的模式演化 |
为了说明我们的推理,将通过一个模拟在线商店的使用情景来进行说明,该商店向注册客户出售古董银器。
我们将在该情景中说明的一些要点包括:
设置 PHP 环境的容易程度将 DB2 原生 XML 功能与 PHP 应用程序(包括用 PHP 和 XQuery 编写的 Web 服务)集成的容易程度使用 XQuery、存储过程和视图将业务逻辑和数据转换放到数据库中。 |
按分列结构在列中存储 XML 文档使用 XQuery 进行搜索和发布在 DB2 存储过程和视图中支持 XML使用 XML 索引提高性能 |
为突出使用 DB2 原生 XML 支持对 PHP 应用程序代码和关系模式设计的影响,该情景将创建一个并行环境,该环境使用不包含任何 XML 功能的数据库(例如,MySQL)。我们将研究这两个环境在应用程序代码、数据库查询和关系模式方面的差异。还将说明选择特定代码、模式或查询以及备选方案(如果可能)的理由。
情景
该情景模拟向注册客户出售古董银器的在线商店。因为该情景的一个目的是说明不同的数据库环境以及它们对应用程序代码的影响,所以我们将对两个应用程序进行同时说明,一个应用程序使用 DB2 原生 XML,另一个应用程序使用类似 MySQL 的开放源码 RDMS,具有有限的 XML 功能或没有任何 XML 功能。
因此,访问 Web 站点的客户将看到一个包含两个垂直面板的页面。每个面板都将显示同一应用程序的一个版本,提供相同的用户体验,但在后端使用不同的数据库:
具有原生 XML 支持的 DB2其他 RDBMS(在本例中为不使用任何 XML 功能的 DB2) |
为显示应用程序代码的差异,每个面板进一步分为两个水平框架,上面的框架显示在线商店,下面部分显示代码段。当用户进行任何操作(如单击某一种类或产品图像)时,上面部分都会生成一个新页面。下面部分显示创建此页面所需的代码。
图 1. 示例应用程序面板
498)this.width=498;’ onmousewheel = ‘javascript:return big(this)’ height=355 alt=示例应用程序面板 src=”/files/uploadimg/20060809/1134560.jpg” width=572>
这将说明,虽然在任何一个应用程序中用户的体验没有变化,但代码复杂性发生了很大变化。这种对比将突出用 PHP 编写的普通 SMB 应用程序使用 DB2 原生 XML 功能的好处。
注意:我们对此情景的假设是业务数据已经是 XML 格式的,尽管数据库可能没有任何 XML 功能。这将产生可用于数据库的使用 XML 功能的 PHP 应用程序代码(如简单 DOM)。具有有限 XML 功能或无 XML 功能的数据库将 XML 数据存储为 CLOB/BLOB 数据类型,或分割到关系字段中。
在 Web 站点中浏览时的功能和用户体验
Web 站点将为用户提供索引,列出商店中所有可用银器的种类和品牌。用户单击某一种类或品牌时,将显示该种类或品牌的货品列表。选择列表中的任何货品都会在页面中显示该货品的详细信息。用户可以将这些货品添加到购物车中。一旦用户提交了订单,将会创建采购订单并根据此采购订单向用户提供发票。用户可以随时检查购物车中的货品。用户还能够得到他们过去已订购的所有货品的报告。
应用程序体系结构
图 2 显示了示例应用程序的基本体系结构。
图 2. 应用程序体系结构
498)this.width=498;’ onmousewheel = ‘javascript:return big(this)’ height=431 alt=应用程序体系结构 src=”/files/uploadimg/20060809/1134561.jpg” width=473>
关系和 XML 模式
XML 文档和模式
原生 XML 存储不需要 XML 列与特定 XML 模式关联。需要对插入到数据库中的 XML 文档进行的任何验证都在插入语句中使用 SQL/XML 函数显式地进行。附录中包含 XML 文档示例。
关系模式
对于这两个数据库,用于存储这些 XML 文档的关系模式将有所不同。
对于 DB2 原生 XML,将有三个表,每个表包含两列。
图 3. DB2 原生 XML 模式
498)this.width=498;’ onmousewheel = ‘javascript:return big(this)’ height=72 src=”/files/uploadimg/20060809/1134562.jpg” width=460>
对于无 XML 支持的 RDBMS,将有四个表,每个表包含多个列:
图 4. 无 XML 支持的情景的关系模式
498)this.width=498;’ onmousewheel = ‘javascript:return big(this)’ height=261 src=”/files/uploadimg/20060809/1134563.jpg” width=563>
可以看出,与无 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. 主页
498)this.width=498;’ onmousewheel = ‘javascript:return big(this)’ height=451 alt=主页 src=”/files/uploadimg/20060809/1134564.jpg” width=438>
创建种类和品牌的索引列表
索引通过查询数据库中所有产品的惟一种类和品牌的列表而创建。启动应用程序时将创建此列表。
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 “ “;} |
这两种情况中的应用程序代码相似。创建 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. 某一种类中的货品列表
498)this.width=498;’ onmousewheel = ‘javascript:return big(this)’ height=356 alt=某一种类中的货品列表 src=”/files/uploadimg/20060809/1134565.jpg” width=572>
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 |