级别: 中级 Stephen B Morris, CTO, Omey Communications
2009 年 8 月 13 日 本文探讨了创建、存储和操纵可选 XML 关系数据所需的软件 — 是这份共 2 部分的系列文章的第 2 部分。本文提供了完全可以运行的代码示例,使用的软件包括 Java™ Architecture for XML Binding (JAXB)、Java Persistence API (JPA)/Hibernate、内存数据库、持久性相关数据库。
在本系列的 关系数据库中的可选 XML,第 1 部分:需要 null 值吗? 中,您了解了如何使用 JAXB 将 XML 域转换为 Java 代码。这种域间转换的概念在很多领域的学习中都很有用 — 例如,信号处理领域中从时间域移入频率域。一个相关的例子是 XSLT,其中样式表用于将
XML 转换为其他文本格式,比如 HTML。现在,来看一下
JAXB 所需的设置。
设置 JAXB
通常,我不会过多地介绍软件安装,但是对于 Java Web Services
Developer Pack 而言(请参见 参考资料),安装比通常情况下要复杂一些。因此,我会介绍一下这个过程,帮您顺利完成安装,使其正常运行。
首先,要 下载该软件包的版本 2.0。Web 服务下载相对较小 —23MB,不会花太长时间。如果在读过本文后还想了解更多内容,可以下载 Web 服务文档包(可在同一页面上找到)。要运行 InstallShield 向导,双击下载的可执行文件,然后按照说明操作。我建议不要安装 Web
容器。另外,我使用 C:\Sun\jwsdp-2.0 作为安装目录。
 |
常用缩写词
- API:应用程序编程接口(Application programming interface)
- HTML:超文本标记语言(Hypertext Markup Language)
- SQL:结构化查询语言(Structured Query Language)
- XML:可扩展标记语言(Extensible Markup Language)
- XSD:XML 模式定义(XML Schema Definition)
- XSLT:可扩展样式表语言转换(Extensible Stylesheet Language Transformation)
|
|
安装好下载的软件后只有一件最重要的事要做:在 %JAVA_HOME%\jre\lib
目录内,创建名为 endorsed 的文件夹。然后,将 C:\Sun\jwsdp-2.0\lib 文件夹的内容粘贴到新
文件夹 %JAVA_HOME%\jre\lib\endorsed 中。%JAVA_HOME% 环境变量
是到 Java 软件开发工具包 (JDK) 安装的完整路径。如果愿意,可以在命令行中使用 java.endorsed.dirs 系统目录,创建新目录是较为简单的一种方法。
还需要一个 Apache Ant 的副本(请参见 参考资料)。下载并安装 Ant,确保可执行程序位于系统路径中。
运行源代码
本文的源代码,作为 .zip 文件包含在
下载 中。要查看本文提供的示例,只需将文件内容解压到文件夹(比如 C:\article_code)中即可。在该目录下应该看到两个文件夹:一个叫做 unmarshal-read,另一个叫做 dbcode。
运行一个 JAXB 示例
要确保正确配置了环境,在一个示例文件夹(比如,C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read)中打开 DOS 控制台。尝试在该文件夹中运行 Ant 目标,如 清单 1 所示。
清单 1. 运行 Ant
C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read>ant -p
Buildfile: build.xml
This sample application demonstrates how to unmarshal
an instance document into a Java content tree and access
data contained within it.
Main targets:
clean Deletes all the generated artifacts.
compile Compile all Java source files
javadoc Generates javadoc
run Run the sample app
Default target: run
|
看到程序输出如 清单 1 所示时,就可以确定安装成功了。在传递过程中,注意 ant -p 命令可以很方便的确定某个 build.xml 文件支持的目标。它使您能在不运行任何目标的情况下,窥探到 build 脚本的内部。
要运行示例程序,只需键入 ant 命令,不需要参数。会看到程序输出如 清单 2 所示。
清单 2. 运行 JAXB 示例程序
C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read>ant
Buildfile: build.xml
compile:
[echo] Compiling the schema...
[xjc] C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read\gen-src\primer.po is not
found and thus excluded from the dependency check
[xjc] Compiling file:/C:/Sun/jwsdp-2.0/jaxb/samples/unmarshal-read/po.xsd
[xjc] Writing output to C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read\gen-src
[echo] Compiling the java source files...
[javac] Compiling 4 source files to
C:\Sun\jwsdp-2.0\jaxb\samples\unmarshal-read\classes
run:
[echo] Running the sample application...
[java] Ship the following items to:
[java] Alice Smith
[java] 123 Maple Street
[java] Cambridge, MA 12345
[java] US
[java]
[java] 5 copies of "Nosferatu - Special Edition (1929)"
[java] 3 copies of "The Mummy (1959)"
[java] 3 copies of "Godzilla and Mothra:
Battle for Earth/Godzilla vs. King Ghidora"
BUILD SUCCESSFUL
Total time: 9 seconds
|
清单 2 展示了一个完整的 JAXB 程序。稍后会看到这种技术的另一个示例,用于将
XML 转换为 Java 代码。(这里需要解释一下术语。谈到将 XML 解组(unmarshalling)为 Java 代码:编组(marshall)指的是将 Java 代码转换为 XML,解组 就是将 XML 转换为 Java 代码)。现在,看一下包含可选元素的 XML 数据。
可选元素的 XSD 规范
清单 3 展示了 xsd:complexType
元素,您一定会感兴趣的。
清单 3. 带有可选元素的 XSD 类型定义
<xsd:complexType name="PurchaseOrderType">
<xsd:sequence>
<xsd:element name="shipTo" type="EuropeanAddress"/>
<xsd:element name="billTo" type="EuropeanAddress"/>
<xsd:element ref="comment" minOccurs="0"/>
<xsd:element name="items" type="Items"/>
</xsd:sequence>
<xsd:attribute name="orderDate" type="xsd:date"/>
</xsd:complexType>
|
清单 3 的内容是 po.xsd 文件的一部分,您会看到它是一个
XML 模式。清单 4 展示了一个符合 po.xsd 模式的 XML 文件(名为 po.xml)。
清单 4. 示例 XML 文档
<?xml version="1.0"?>
<purchaseOrder orderDate="1999-10-20">
<shipTo country="IE">
<name>Alice Smith</name>
<street>123 Maple Street</street>
<city>Cambridge</city>
<postcode>12345>/postcode>
</shipTo>
<billTo country="IE">
<name>Robert Smith</name>
<street>8 Oak Avenue</street>
<city>Cambridge</city>
<postcode>12345</postcode>
</billTo>
<items>
<item partNum="242-NO" >
<productName>Nosferatu - Special Edition
(1929)>/productName>
<quantity>5</quantity>
<USPrice>19.99</USPrice>
</item>
<item partNum="242-MU" >
<productName>The Mummy (1959)</productName>
<quantity>3</quantity>
<USPrice>19.98</USPrice>
</item>
<item partNum="242-GZ" >
<productName>Godzilla and Mothra: Battle for
Earth/Godzilla vs. King Ghidora</productName>
<quantity>3</quantity>
<USPrice>27.95</USPrice>
</item>
<>/items>
</purchaseOrder>
|
注意 清单 4 中的 XML 不包含任何注释字段。如
清单 3 中所示,注释字段是可选的。
现在要将 清单 4 中的 XML 数据解组为一个或多个 Java 类。为此,在源代码文件夹 unmarshal-read 中打开 DOS 控制台。然后,运行以下命令:
ant compile -Djwsdp.home=C:\Sun\jwsdp-2.0
|
清单 5 展示了预期的输出结果。
清单 5. XML 到 Java 的转换
C:\article_code\unmarshal-read>ant compile -
Djwsdp.home=C:\Sun\jwsdp-2.0
Buildfile: build.xml
compile:
[echo] Compiling the schema...
[mkdir] Created dir: C:\article_code\unmarshal-
read\gen-src
[xjc] C:\article_code\unmarshal-read\gen-
src\primer.po is not found and thus excluded from the
dependency check
[xjc] Compiling file:/C:/article_code/unmarshal-
read/po.xsd
[xjc] Writing output to C:\article_code\unmarshal-
read\gen-src
[echo] Compiling the java source files...
[mkdir] Created dir: C:\article_code\unmarshal-
read\classes
[javac] Compiling 5 source files to
C:\article_code\unmarshal-read\classes
BUILD SUCCESSFUL
Total time: 5 seconds
|
清单 5 展示了 JAXB 的神奇。它从 XML 文件生成 Java 类并由清单 6 中展示的 Java 程序驱动。
清单 6. 驱动解组的代码
public static void main( String[] args ) {
try {
// create a JAXBContext capable of handling classes generated into
// the primer.po package
JAXBContext jc = JAXBContext.newInstance( "primer.po" );
// create an Unmarshaller
Unmarshaller u = jc.createUnmarshaller();
// unmarshal a po instance document into a tree of Java content
// objects composed of classes from the primer.po package.
JAXBElement<?> poElement =
(JAXBElement<?>)u.unmarshal( new FileInputStream( "po.xml" ) );
PurchaseOrderType po = (PurchaseOrderType)poElement.getValue();
// examine some of the content in the PurchaseOrder
System.out.println( "Ship the following items to: " );
// display the shipping address
EuropeanAddress address = po.getShipTo();
displayAddress( address );
// display the items
Items items = po.getItems();
displayItems( items );
} catch( JAXBException je ) {
je.printStackTrace();
} catch( IOException ioe ) {
ioe.printStackTrace();
}
}
|
清单 6 中的代码由以下主要步骤组成:
- 创建 JAXB 上下文。
- 使用上下文创建解组器。
- 解组文件 po.xml。
- 创建
PurchaseOrderType 类的对象。
- 提取并显示来自对象的一些元素。
清单 6 的代码包含了众多功能。现在,您拥有了原始 XML 数据的 Java 表示。现在,要将这个数据推入关系数据库。
从 Java 代码转换为数据库实体
Java 注释的力量真正显现出来是在您希望持久化
Java 类的时候。这种从 Java 代码到关系域的映射叫做对象关系映射(ORM)。清单 7 提供了映射 ShippingAddress 类的示例。
清单 7. ShippingAddress 的 ORM
@Embeddable
public class ShippingAddress {
@Column(name = "SHIPPING_ADDRESS_STREET")
private String street;
@Column(name = "SHIPPING_ADDRESS_CITY")
private String city;
ShippingAddress() {}
public ShippingAddress(String street, String city) {
this.street = street;
this.city = city;
}
public String getStreet() {
return street;
}
private void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
private void setCity(String city) {
this.city = city;
}
}
|
如果您看看 清单 7 并暂时忘记注释的事情,会发现它就是一个简单的 Java 类,叫做 ShippingAddress。事实上,使用集成开发环境 (IDE),比如 Eclipse,您可以轻松地自动生成 清单 7 的大部分代码 — 也就是构造函数以及 setter 和 getter 方法。注释只是用于将类及其属性映射到关系域。@Embeddable
注释表明这整个类都可以被嵌入到另一个类中(叫做实体类),如 清单 8 中所示。
清单 8. 实体类
@Entity
@Table(name = "PURCHASE_ORDERS")
public class PurchaseOrder {
@Id @GeneratedValue
@Column(name = "PO_ID")
private Long id;
@Embedded
private ShippingAddress shippingAddress;
@Embedded
private BillingAddress billingAddress;
@Column(name = "COMMENT")
private String comment;
@Column(nullable=false, name="COMMENT_ENTERED",
columnDefinition="boolean default false")
private boolean commentEntered;
// More methods here
}
|
注意,清单 8 中,ShippingAddress 类实例被嵌入到 PurchaseOrder
类中的方式。这个实体类如何与数据库联系起来?
运行数据库代码
要运行数据库代码,在名为 dbcode 的文件夹中打开 DOS 控制台。通过键入 start
命令,创建两个额外的 DOS 控制台。在一个 DOS 控制台中,运行 Ant 目标 ant startdb。
这一目标启动内存中的 HyperSQL DataBase (HSQLDB)。在该命令后,您会看到类似于 清单 9 中的输出。
清单 9. 运行 HSQLDB
C:\article_code\dbcode>ant startdb
Buildfile: build.xml
startdb:
[java] [Server@1e0be38]: [Thread[main,5,main]]: checkRunning(false)
entered
[java] [Server@1e0be38]: [Thread[main,5,main]]: checkRunning(false) exited
[java] [Server@1e0be38]: Startup sequence initiated from main() method
[java] [Server@1e0be38]: Loaded properties from
[C:\article_code\dbcode\server.properties]
[java] [Server@1e0be38]: Initiating startup sequence...
[java] [Server@1e0be38]: Server socket opened successfully in 62 ms.
[java] [Server@1e0be38]: Database [index=0, id=0, db=file:database/db,
alias=] opened successfully in 313 ms.
[java] [Server@1e0be38]: Startup sequence completed in 375 ms.
[java] [Server@1e0be38]: 2009-04-10 09:54:26.625 HSQLDB server 1.8.0 is
online
[java] [Server@1e0be38]: To close normally, connect and execute SHUTDOWN
SQL
[java] [Server@1e0be38]: From command line, use [Ctrl]+[C] to abort
abruptly
|
在 清单 9 中运行 Ant 目标之后,就会有一个运行中的数据库管理器(目前还未运行数据库)。下一项任务是运行 schemaexport
Ant 目标,这会创建数据库模式,如 清单 10 所示。
清单 10. 数据库创建
C:\article_code\dbcode>ant schemaexport
Buildfile: build.xml
compile:
[mkdir] Created dir: C:\article_code\dbcode\build
[javac] Compiling 4 source files to C:\article_code\dbcode\build
copymetafiles:
[copy] Copying 2 files to C:\article_code\dbcode\build
schemaexport:
[hibernatetool] Executing Hibernate Tool with a JPA Configuration
[hibernatetool] 1. task: hbm2ddl (Generates database schema)
[hibernatetool]
[hibernatetool] drop table PURCHASE_ORDERS if exists;
[hibernatetool]
[hibernatetool] create table PURCHASE_ORDERS (
[hibernatetool] PO_ID bigint generated by default as identity (start
with 1),
[hibernatetool] SHIPPING_ADDRESS_STREET varchar(255),
[hibernatetool] SHIPPING_ADDRESS_CITY varchar(255),
[hibernatetool] BILLING_ADDRESS_STREET varchar(255),
[hibernatetool] BILLING_ADDRESS_CITY varchar(255),
[hibernatetool] COMMENT varchar(255),
[hibernatetool] COMMENT_ENTERED boolean default false not null,
[hibernatetool] primary key (PO_ID)
[hibernatetool] );
BUILD SUCCESSFUL
Total time: 8 seconds
|
注意 清单 10 中的 SQL 语句 create table PURCHASE_ORDERS。这时,您就拥有了一个常驻数据库,可以通过
Ant run 目标向其中插入数据。因此,在第三个 DOS 控制台,执行 ant run
命令,如 清单 11 所示。
清单 11. 运行程序
C:\article_code\dbcode>ant run
Buildfile: build.xml
compile:
copymetafiles:
run:
[java] Value of CommentEntered false
[java] 1 purchase order(s) found:
[java] Broad Street
[java] Boston
[java] Comment entered: true
BUILD SUCCESSFUL
Total time: 3 seconds
|
清单 11 指出数据库已成功填充。要查看数据库内容,运行 ant dbmanager
命令(从另一个 DOS 控制台)。您会看到打开一个窗口,如
图 1 所示。
图 1. 数据库管理器应用程序
图 1 是一个简单的图形数据库管理器应用程序,连接到了您刚才创建的数据库。要对数据库运行一个查询,单击 Command > SELECT,然后完成 SQL 语句:
SELECT * FROM PURCHASE_ORDERS
|
单击 Execute SQL。会看到类似
图 2 中的片段。
图 2. 填充的数据库
如果将 图 1 中的数据库列与 清单 6 和
清单 7 相比较,会发现 Java 代码实际上已经被包含到了数据库中。这当然多亏了 ORM 技术。图 1 中的数据库现在包含两行;清单 12 展示了创建这两行的 Java 代码。
清单 12. 持久化两个对象
PurchaseOrder purchaseOrder = new PurchaseOrder();
purchaseOrder.setShippingAddress(new ShippingAddress("Broad Street",
"Boston"));
purchaseOrder.setBillingAddress(new BillingAddress("Broad Street", "Boston"));
purchaseOrder.setComment("A COMMENT");
purchaseOrder.setCommentEntered(true);
PurchaseOrder purchaseOrder1 = new PurchaseOrder();
purchaseOrder1.setShippingAddress(new ShippingAddress("Broader Street", "New
York"));
purchaseOrder1.setBillingAddress(new BillingAddress("Broader Street", "New
York"));
em.persist(purchaseOrder);
em.persist(purchaseOrder1);
|
清单 12 中的代码实例化了 PurchaseOrder
类的两个对象。这些对象被填充,然后您通过调用
em.persist() 将它们持久化到数据库中。注意,对于
purchaseOrder 对象而言,您显示地插入了一个注释;但是对于 purchaseOrder1 而言,则没有注释。记住这些,在 图 2 中,注意
COMMENT 列的某一行有一个空的条目。但是,相应的 COMMENT_ENTERED
列却从不为空;它不是 True 就是 False。
确定某一个行是否包含注释时,不再需要检查 COMMENT 列的 null 值。只要读取
COMMENT_ENTERED 列的值,如果该值为 True,则 COMMENT 列具有一个值。换句话说,这说明您正确建模和实现了可选 XML 数据。
结束语
将 XML 数据转换成关系数据似乎有点麻烦。从
XSD 和 XML 文件开始并使用 JAXB 将其转换为相应的 Java 类。然后,使用 ORM 技术填充数据库。总之要完成很多的工作。
显然,本文涉及的问题很小。但是如果扩展到一个一般规模的企业应用程序,通常会有很多
XML 对象。这些是业务域对象,在 XML 中定义它们就很有意义了。一方面,XML 定义允许非程序员进行对象定义。另一方面您可以随后使用
JAXB 将
XML 无缝地转换为 Java 实体。
一旦 XML 对象位于 Java 域中,注释的力量就会显现出来,允许您稍微修改一下 Java 类就可以使其成为持久实体。本文只是对将数据转换到关系数据库的一个简短介绍。
熟练使用 Java 对象关系注释有助于生成良好的模式定义。熟练 在这里意味着对注释元素的正确使用,如
nullable=false,仅允许被选定的项具有 “空值”。与
COLUMN_ENTERED 值一起使用,您就不用担心插入 null 值。这就是实践中的良好设计!
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| 本文的源代码 | code.zip | 5700KB | HTTP |
|---|
参考资料 学习
获得产品和技术
讨论
关于作者  | |  | Stephen Morris 是爱尔兰的一名独立作家/顾问。凭借在企业开发和网络应用程序方面的丰富经验,Stephen 曾在一些世界最大的网络公司参与过各种软件项目,包括基于 J2EE/J2SE 的网络管理系统,帐单编制应用程序、财务系统、移植和开发 SNMP 实体、网络设备技术和一些移动计算应用程序。他拥有计算机科学的硕士学位,并且拥有三项网络管理方面的专利。他是 Moving Your Career Up the Value Chain: Building Specialized Development Skills in a Global Economy 一书的作者,并且发表过多篇有关网络管理和其他主题的文章。 |
对本文的评价
|