映射 DTD 到数据库[下]

3.4. 映射次序

本节讨论对象-关系映射如何处理次序。

3.4.1. 同级次序, 层次次序, 和文档次序

同级(Sibling)意味着“兄妹”。就是说,同级元素或 PCDATA 是有相同父元素的元素或 PCDATA。换句话说,它们出现在同一个内容模型中。例如,如果在前面的章节中文档被表示为一棵树,这很容易的显示出那些元素是同级的: 这些元素在这个层次的第二级上,都有 A 作为它们的父元素。

A
_____|
| | | | | | | | |
This text C makes B no sense C except as B an example
| | | |
cc bbbb cccc bb

注意在第这个层次的第三级的元素不是同级的,因为它们不共享相同的父元素。这还指出了同级次序,它是在它们的父元素中子元素出现的次序,和层次次序,它是子元素在表示文档的树中出现在的级别,二者之间的不同。不同的还有文档次序,它是元素和文本在一个 XML 文档中出现的次序。例如:

同级次序(只有一个同级元素的地方次序不显示):

A
_____|
| | | | | | | | |
This text C makes B no sense C except as B an example
1 2 3 4 5 6 7 8 9
| | | |
cc bbbb cccc bb

层次次序:

1 A
_____|
| | | | | | | | |
2 This text C makes B no sense C except as B an example
| | | |
3 cc bbbb cccc bb

文档次序:

A
1
_____|
| | | | | | | | |
This text C makes B no sense C except as B an example
2 3 5 6 8 9 11 12 14
| | | |
cc bbbb cccc bb
4 7 10 13

依据 XML 规定,同级次序是重要的。实际上,这依赖于应用。例如,在以数据为中心的应用中,使用 XML 文档来传载一个对象或表,同级次序通常是无关紧要的,因为面向对象语言没有在类属性之间的次序的概念。类似的,关系数据库没有在列之间的次序的概念。所以,同级次序在下列文档中不是重要的:

1<part>
2<number>123</number>
3<desc>Turkey wrench</desc>
4<price>10.95</price>
5</part>
1<part>
2<price>10.95</price>
3<desc>Turkey wrench</desc>
4<number>123</number>
5</part>

它们都被映射成下列对象和表中的行:

对象 表
========================= ===================================
Table Parts
object part { -------------------------------
number = 123 ==> Number Desc Price
desc = "Turkey wrench" ------ ------------- -----
price = 10.95 123 Turkey wrench 10.95

(对此的一个主要的例外是在以数据为中心的文档必须匹配一个特定的 DTD 的时候。这在一个应用必须验证文档的时候发生,比如在它们来自未知或不被信任的来源的时候。尽管在这种情况下 XML Schemas 的“all 组”通过允许一组子元素以任何次序出现能帮上忙,但它们不支持重复子元素。)

在另一方面,在以文档为中心的应用中,通常文档是为了人的消费而设计的,同级次序是非常重要的。例如,我很可能喜欢第一个评述而不是第二个:

1<review>
2<p>Ronald Bourret 是一个   
3<b>优秀的作家</b>。   
4只有<b>傻瓜</b>   
5才不去读他的作品。</p>
6</review>
1<review>
2<p>Ronald Bourret 是一个   
3<b>傻瓜</b>。只有   
4<b>优秀的作家</b>   
5才不去读他的作品。</p>
6</review>

对象-关系映射可以保留同级次序,下面将会见到,尽管实际上很少有产品支持它。通过把到简单元素类型的引用映射到在表中的列,和把到复杂元素类型的引用映射成主键、外键联系,它本能的保留层次次序。在保留了层次次序和同级次序的时候就保留了文档次序。

3.4.2. 映射同级次序

因为面向对象语言没有类属性之间次序的概念,而关系数据库没有列之间次序的概念,必须与数据值独立的存储同级次序值。这么做的一种方式是介入在其中存储次序值的独立类属性和列。这么做的另一种方式在映射自身中存储次序值。

3.4.2.1. 次序属性和列

使用次序类属性和次序列来存储次序值。它们对立于数据类属性和数据列。对认定次序很重要的每个被引用的元素类型或 PCDATA 都需要一个类属性或列。例如,考虑上面的混合内容的例子。下面把在 DTD 中的同级次序映射到次序类属性:

DTD 类
=============================== ========================

class A {
String[] pcdata;
int[] pcdataOrder;

String[] b;

==> int[] bOrder;

String[] c;
int[] cOrder;
}
并接着映射到次序列:

类 表
======================== ========================================

Table PCDATA
class A { -----Column a_fk
String[] pcdata; / Column pcdata
int[] pcdataOrder; / Column pcdataOrder
String[] b; Table A / Table B
int[] bOrder; ==> Column a_pk--------Column a_fk
String[] c; \ Column b
int[] cOrder; \ Column bOrder
} \ Table C
\----Column a_fk
Column c
Column cOrder

注意在标中存储的次序类属性与它们定序的类属性是并列的。

下面的例子展示使用次序属性来保留在"makes-no-sense"例子中的同级次序。这里要注意的重要的事情是所有次序类属性共享相同的次序空间。出现在一个次序类属性中一个次序值不会出现在另一个次序类属性中。

类 表
================================= =====================================

Table PCDATA
a_fk pcdata pcdataOrder
---- ----------- -----------
1 This text 1
object a { 1 makes 3
pcdata = {"This text ", 1 no sense 5
" makes ", 1 except as 7
" no sense ", Table A 1 an example. 9
" except as", a_pk
" an example."} ==> ---- Table B
pcdataOrder = {1, 3, 5, 7, 9} 1 a_fk b bOrder
b = {"bbbb", "bb"} ---- ---- ------
bOrder = {4, 8} 1 bbbb 4
c = {"cc", "cccc"} 1 bb 8
cOrder = {2, 6}
} Table C
a_fk c cOrder
---- ---- ------
1 cc 2
1 cccc 6
尽管次序类属性最常用来维护在混合内容中的次序,它们也可以与元素内容一起使用。例如,考虑下面的元素类型定义。因为在 A 中 B 可以出现任意次,它被存储在独立的属性表中。没有次序类属性,将无法确定如何定序 B 子元素。(注意这里不能使用行次序,因为关系数据库不保证以任何特定的次序返回行。)

3.4.2.2. 在映射中存储次序

在许多情况下,同级次序只在验证的时候是重要的;除了必须能验证一个文档之外,应用自身不关心同级次序。特别是在以数据为中心的文档中元素内容。在这种情况下,在映射自身中存储次序信息就足够了。

例如,给出下列内容模型,映射可以存储 A 的子元素的次序是 B 然后 C 然后 D 的信息:

特别是,限制为在映射中存储次序信息。例如,考虑下面的内容模型:

构造匹配这个内容模型的文档要求软件首先决定能获得多少数据来构造 B 元素。如果只有构造一个 B 元素的足够数据,它不能第一个 B 元素,因为第二个 B 元素是必须的。

让多数软件这么麻烦的这么做是不大可能的。转而,只对组织所有相同元素类型的同级元素在一起的那些内容模型提供合理的限制。对于许多以数据为中心的内容模型这是足够的,并可以通过在映射中存储每个元素在内容模型中的位置来实现。

例如,在下列元素中同级元素的次序可以被如此映射。注意在第三个内容模型中,Author 和 Editor 二者都可以被赋予相同的次序值或不同的值;如果它们被赋予不同的值,一种类型的所有元素都都会出现在其他类型的任何元素的前面。

当次序信息只存储在映射中的时候,只要内容模型包含多于一个相同类型的元素,文档的无损流通(round-tripping)就是不可能的。例如,考虑下面的内容模型:

尽管映射可以告诉软件所有 B 元素必须出现在 C 元素起前面,它不能指定 B 元素的次序。所以,如果数据从包含这种内容模型的文档传输到数据库然后再传输回来,就不能保证 B 元素按原始文档中的次序出现。幸运的是,对以数据为中心的文档这通常不是问题。

3.5. 映射属性

如同前面所见到的,把属性映射为标量类属性。本节将讨论这种映射的详情,还有其他一些要点。

3.5.1. 映射单值和多值属性

有两种不同的属性: 单值(CDATA、ID、IDREF、NMTOKEN、ENTITY、NOTATION 和枚举)和多值(IDREFS、 NMTOKENS 和 ENTITIES)。预期的可能是,把它们映射成单值类属性(接者是列)和多值类属性(接着是属性表)。例如:

DTD 类 表
============================ ============ ===========

class A { Table A

==> String c; ==> Column C

String d; Column D

}
和:

DTD 类 表
======================== ============== ==============

class A { Table A

==> String c; ==> Column b

String[] d; Column c

} Table D
Column a_fk
Column d

依据 XML 信息集属性出现的次序是不重要的。例如,下列两个 XML 被认为是等同的。故此,不需要次序类属性来维护属性出现的次序,尽管这么做是完全可能的。

1<a b="bbb" c="ccc" d="ddd"></a>
1<a b="bbb" c="ccc" d="ddd"></a>

在另一方面,值出现在多值属性中的次序被认为是重要的。这同于同级元素和 PCDATA 的情况,可以使用次序类属性来维护关于在多值属性中值出现次序的信息。但是,在用于同级元素和 PCDATA 的次序类属性、和用于多值属性的次序类属性之间有一个重要的区别: 用于多值属性的每个次序类属性都有它自己的次序空间。这可以在下面的例子中见到:

XML 对象
=============== =========================

object a {

1<a "ee",="" "ff"}="" b='{"dd",' c="gg hh"></a>

==> bOrder = {1, 2, 3}
c = {"gg", "hh"}
cOrder = {1, 2}
}

提醒读者注意次序类属性在对象级别不是严格必须的;可以用数组次序替代。但是,在关系数据库中是必须的,因为这里没有行次序的概念。

3.5.2. 映射 ID/IDREF(S) 属性

ID 属性用来唯一的标识在 XML 文档中的元素。使用 IDREF 和 IDREFS 属性,通过提及后面的元素的 ID 来把一个元素与另一个元素关联起来。通常在不能通过把一个元素嵌套在另一个元素之中来形成这种关联的时候这么做。例如,考虑下面的有向图:

A
/ \
B C
\ /
D

它可以在 XML 文档中表示为:

 1<a>
 2<b ref_d="1">   
 3...   
 4</b>
 5<c ref_d="1">   
 6...   
 7</c>
 8<d id="1">   
 9...   
10</d>
11</a>

ID/IDREF(S) 属性映射为主键、外键联系。例如,上面的文档可以在数据库中存储为下列表和列:

Table A
Column a_pk
...
/ \
/ \
Table B Table C
Column a_fk Column a_fk
Column ref_d Column ref_d
... ...
\ /
\ /
Table D
Column a_fk
Column id
...

在数据库中存储 ID/IDREF(S) 属性的时候,数据传输软件需要小心的一件事是 ID 只保证在一个给定的 XML 文档内是唯一的。所以,如果来自多于一个文档的数据存储在相同的表中,则不能保证 ID 是唯一的。这个问题的解决是以某种方式“修饰” ID。可以通过把属性映射成两列,一列包含对于每个文档是唯一的一个值,另一列包含 ID,或者修饰 ID 自身,比如用加以唯一值的前缀来完成。

在数据从数据库传输到 XML 文档的时候存在类似的问题。如果取回的数据起源于多于一个文档,则数据传输软件需要确保 ID 值是唯一的。这可能涉及到改变一个或多个值,连同引用它们的所有 IDREF(S) 属性的值。

目前,多数产品不把 ID/IDREF 作为有别于其他属性的属性来支持。

3.5.3. 映射注记属性

在 XML 文档中使用注记(notation)来提醒应用如何处理一个元素或未分析的实体。例如,下列的 "xhtml" 注记可以告诉应用这个元素包含 XHTML 并应当使用浏览器来显示:

1<desc type="xhtml">
2<html>
3<head><title>Turkey wrench</title></head>
4<body>
5<p>A very <b>big</b> turkey wrench.</p>
6</body>
7</html>
8</desc>

注记属性和它们的值对于对象-关系映射通常没有什么意义;把它们作为简单的另一种属性来对待。

对此的唯一的例外发生在注记指示包含的文本的数据类型的时候。例如,注记“base64”可以告诉应用包含二进制数据的一个元素被编码为 Base64 (映射二进制数据到 US-ASCII 的一个子集的一种 MIME 编码)。在多数情况下,这种信息只对生成映射的软件有意义。它可以使用这种信息来映射元素类型到二进制值类属性并接着到 BLOB (二进制大对象)。在这些情况下,映射自身不使用这些信息。从元素到到 BLOB 的映射独立于注记包含数据类型信息的事实。

对此的唯一的例外是当数据传输软件复杂到基于注记值来在运行时间切换映射的时候。在这种情况下,每种可能的注记被映射成一种数据类型,接着使用它来转换数据。

3.5.4. 映射 ENTITY/ENTITIES 属性

使用 ENTITY 和 ENTITIES 属性把未分析的、外部数据(比如一个二进制文件)与 XML 文档关联起来。映射它们同任何其他属性一样,不同的是,在传输数据的时候,用实体替换属性值(在从 XML 传输数据到数据库的时候),或者可以建立一个新实体并把它的标示符存储为属性值(在从数据库传输数据到 XML 的时候)。因为未分析实体值可以动态生成,映射指定在数据库中存储值还是实体 URI 是个好主意。

因为未分析实体总是有相关的注记,在决定实体的数据类型(在映射时间或运行时间)的时候可能用到这些注记。

3.6. 可供选择的映射

在前面的章节中,我们已经描述了如何映射 DTD 到数据库。实际上,这些描述是不完整的,因为还有一些其他方式来完成映射。在本节中,我们将讨论两种最重要的替代者。

3.6.1. 映射复杂元素类型到标量类型

尽管复杂元素类型通常映射成类并接着映射成表,也可能把它们映射成标量类型。换句话说,到复杂类型的引用可以被映射成标量类属性。这种类属性的值一般是元素内容,串行化为 XML。在元素的值之作为整体才有意义并且不应该分解为更小的部分的时候这是有用的。

例如,考虑给出关于 part 的信息的一个 XML 文档。如果某个子元素被部分的用 XHTML 描述,进一步分解它可能没有意义。如同我们已经描述的那样,这将导致数据被分散到许多表中;italic 字一个表,bold 字一个表,用在 hyperlink 中的字一个表,等等。所以,最好在一个单一列中存储这些描述:

DTD 类 表
=========================== =============== ==============

class Part { Table Part

==> String num; ==> Column num

}

例如,下面的描述将如此存储:

1<part>
2<number>127</number> Table Part   
3<desc> Num Desc   
4A very <b>big</b> =&gt; \--- ----------------   
5turkey wrench. 127 A very <b>big</b>
6</desc> turkey wrench.   
7</part>

注意存储数据为 XML 确实导致数据传输软件的问题。特别是,软件不能区别标记和数据。例如,应用如何决定在下列文本中的

  1<b> 是一个 <b> 元素还是文本?   
  2  
  3An example of the <b> element is <b>this element</b>.   
  4  
  5对此的一个解决方法是在数据库中使用标签存储实际元素,和使用实体引用来存储字符:   
  6  
  7An example of the **element is <b>this element</b>.   
  8  
  9这么做的问题是非 XML 应用不能按它们希望的那样查找数据库。   
 10  
 11
 12
 13#####  3.6.2. 映射标量类属性到属性表 
 14
 15  
 16尽管单值的、标量值类属性通常映射成列,它们也可以被映射成属性表。这是有用的,例如,在独立于主要表的一个表中存储 BLOB 或不经常使用的类属性。例如:   
 17  
 18类 表   
 19=============== ==================   
 20  
 21class Part { Table Parts   
 22String num; ==&gt; Column num   
 23String desc;   
 24Table Descriptions   
 25} Column num   
 26Column desc   
 27  
 28
 29
 30####  3.7. 结论 
 31
 32  
 33对象-关系映射处理所有 XML 文档,有效的映射到对象,并允许非 XML 应用使用在数据库中的数据。故此,对于以数据为中心的文档是个好主意并且(不奇怪的)在某些中间件、多数启用 XML 的数据库、和多数启用 XML 的对象服务器中用作底层模型。   
 34  
 35应当注意所有这些产品实现了对象-关系映射轻微不同的版本,并且没有一个实现了映射中所有可能的东西。在映射的更加公用的部分中,都把复杂元素映射成类并把到元素类型的引用映射成类属性,同样的使用主键、外键对来连接表。但是,一些只对唯 PCDATA 元素映射列,另一些只对属性映射列,还有其他一些允许二者。类似的,多数这些产品不支持同级次序或混合内容,并且许多在映射期间都不允许用户改变名字。   
 36  
 37对象-关系映射对于普通文档不是个好的选择。首先,在使用混合内容的时候它是低效的。其次,象基于表的映射一样,它不保留物理结构、注释和处理指令。   
 38  
 39  
 40  
 41
 42
 43###  4\. 生成模式 
 44
 45  
 46我们现在考虑如何依据对象-关系映射从 DTD 生成关系数据库模式和反之。因为通过对象-关系映射有很多可能的路径,这里的算法在每次选择时简单的选择最经常使用分支。例如,可以把到唯 PCDATA 元素的单一引用映射成一列或一个单独的属性表。因为最通用的选择是把它们映射一个列,下面的算法从这样的引用生成一列。   
 47  
 48对于面向对象数据库,生成过程是类似的。   
 49  
 50  
 51
 52
 53####  4.1. 从 DTD 生成关系数据库模式 
 54
 55  
 56通过通读 DTD 并处理每个元素类型来生成关系模式:   
 57  
 58
 59
 60  * 复杂元素类型生成带有主键列的类表。 
 61  * 除了在处理内容模型的时候之外忽略简单元素类型。 
 62
 63  
 64  
 65要处理一个类型模型:   
 66  
 67
 68
 69  * 到简单元素类型的单一引用生成列;如果这个引用是可选的(? 操作符),这个列是有空值的。 
 70  * 到简单元素类型的重复引用生成带有外键的属性表。 
 71  * 到复杂元素类型的引用生成在远端(remote)类表中的外键。 
 72  * 在混合内容中的 PCDATA 生成带有一个外键的属性表。 
 73  * 对所有被引用的元素类型和 PCDATA 随意的生成有次序的列。 
 74
 75  
 76  
 77要处理属性:   
 78  
 79
 80
 81  * 单值属性生成列;如果属性是可选的,这个列是有空值的。 
 82  * 多值属性生成带有外键的属性表。 
 83  * 如果一个属性有缺省值,则把它用作列缺省值。 
 84
 85  
 86  
 87下面的例子展示了这个过程是如何工作的。考虑下列 DTD:   
 88  
 89DTD 表   
 90================================================= =================   
 91  
 92<!--ELEMENT Order (OrderNum, Date, CustNum, Item*)-->
 93<!--ELEMENT OrderNum (#PCDATA)-->
 94<!--ELEMENT Date (#PCDATA)-->
 95<!--ELEMENT CustNum (#PCDATA)-->
 96<!--ELEMENT Item (ItemNum, Quantity, Part)-->
 97<!--ELEMENT ItemNum (#PCDATA)-->
 98<!--ELEMENT Quantity (#PCDATA)-->
 99<!--ELEMENT Part (PartNum, Price)-->
100<!--ELEMENT PartNum (#PCDATA)-->
101<!--ELEMENT Price (#PCDATA)-->   
102  
103在第一步,我们为复杂元素类型生成表和这些表的主键:   
104  
105DTD 表   
106================================================= =================   
107  
108<!--ELEMENT Order (OrderNum, Date, CustNum, Item*)--> ==&gt; Table Order   
109<!--ELEMENT OrderNum (#PCDATA)--> Column OrderPK   
110<!--ELEMENT Date (#PCDATA)-->
111<!--ELEMENT CustNum (#PCDATA)-->
112<!--ELEMENT Item (ItemNum, Quantity, Part)--> ==&gt; Table Item   
113<!--ELEMENT ItemNum (#PCDATA)--> Column ItemPK   
114<!--ELEMENT Quantity (#PCDATA)-->
115<!--ELEMENT Part (PartNum, Price)--> ==&gt; Table Part   
116<!--ELEMENT PartNum (#PCDATA)--> Column PartPK   
117<!--ELEMENT Price (#PCDATA)-->   
118  
119在第二步,我们为到简单元素类型的引用生成列:   
120  
121DTD 表   
122================================================= =================   
123  
124<!--ELEMENT Order (OrderNum, Date, CustNum, Item*)--> ==&gt; Table Order   
125<!--ELEMENT OrderNum (#PCDATA)--> Column OrderPK   
126<!--ELEMENT Date (#PCDATA)--> Column OrderNum   
127<!--ELEMENT CustNum (#PCDATA)--> Column Date   
128Column CustNum   
129  
130<!--ELEMENT Item (ItemNum, Quantity, Part)--> ==&gt; Table Item   
131<!--ELEMENT ItemNum (#PCDATA)--> Column ItemPK   
132<!--ELEMENT Quantity (#PCDATA)--> Column ItemNum   
133Column Quantity   
134  
135<!--ELEMENT Part (PartNum, Price)--> ==&gt; Table Part   
136<!--ELEMENT PartNum (#PCDATA)--> Column PartPK   
137<!--ELEMENT Price (#PCDATA)--> Column PartNum   
138Column Price   
139  
140在最后一步,我们为到复杂元素类型的引用生成外键:   
141  
142DTD 表   
143================================================= =================   
144  
145<!--ELEMENT Order (OrderNum, Date, CustNum, Item*)--> ==&gt; Table Order   
146<!--ELEMENT OrderNum (#PCDATA)--> Column OrderPK   
147<!--ELEMENT Date (#PCDATA)--> Column OrderNum   
148<!--ELEMENT CustNum (#PCDATA)--> Column Date   
149**</b></b></b>
Published At
Categories with Web编程
Tagged with
comments powered by Disqus