-- 作者:zhawei
-- 发布时间:5/1/2006 8:41:00 PM
-- 利用JDBC向数据库中插入XML!!
[color=#FF0000][size=4]IBM的教程 和大家分享 本教程是为辅助那些需要从数据库抽取信息然后放入 XML 文档中的 Java TM 开发人员而设计的。 本教程假设您已经大体熟悉 Java 和 XML 语言,特别是熟悉“文档对象模型”(Document Object Model (DOM) )。您应该熟悉 Java 编程,但不必学习前面使用 JDBCTM 进行数据库连接的知识就可以掌握本教程所描述的技术。本教程简要讨论了 SQL 基础。无需 GUI 编程知识,因为应用程序的输入/输出是从命令行处理的。参考资料中的链接包括一些对有关 XML 和 DOM 基础以及详细的 SQL 背景知识的教程的介绍。 访问数据库的过程 create table products ( product_id numeric primary key, product_name varchar(50), base_price numeric, size numeric, unit varchar(10), lower numeric, upper numeric, unit_price numeric ) 设置数据库和驱动程序 首先,确保安装和运行了您选择的任一数据库,并且驱动程序可用。可以从 http://industry.java.sun.com/products/jdbc/drivers 下载 JDBC 驱动程序。 一旦创建了数据库,则创建必要的表。本教程只使用一个表 products。它的结构如左边所示。使用适合于您的数据库的步骤来创建表。 请注意:在通常情况下,应该至少在两个相关的表中表示这个数据;但为简单性起见,本示例只使用一个表。 使用 Java 语言与数据库交互通常由以下几步组成: 1. 装入数据库驱动程序。这可以是一个 JDBC 驱动程序或 JDBC-ODBC 桥。 2. 创建至数据库的 Connection。 3. 创建一个 Statement 对象。该对象实际执行 SQL 或存储过程。 4. 创建一个 ResultSet,然后用执行查询的结果填充(如果目标是检索或直接更新数据)。 5. 从 ResultSet 检索或更新数据。 作为“JavaTM 2 SDK 标准版”发行的一部分,java.sql 包包含用于访问数据库的“JDBC 2.0 核心 API”。作为“Java 2 SDK 企业版”一部分发行的 javax.sql 包包含“JDBC 2.0 可选包 API”。 本教程仅使用“JDBC 2.0 核心 API”中的类。 实例化驱动程序 要访问数据库,首先装入 JDBC 驱动程序。在任何给定的时间都可以使用一些不同的驱动程序;由 DriverManager 通过尝试创建与每个所知的驱动程序的连接来确定要使用哪一个。应用程序将使用第一个成功连接的驱动程序。DriverManager 有两种方法可以知道驱动程序是否存在。 第一种方法是使用 Class.forName() 直接装入它,如本示例中所示。当装入驱动程序类时,它向 DriverManager 注册,如下所示: public class Pricing extends Object { public static void main (String args[]){ //For the JDBC-ODBC bridge, use //driverName = "sun.jdbc.odbc.JdbcOdbcDriver" String driverName = "JData2_0.sql.$Driver"; try { Class.forName(driverName); } catch (ClassNotFoundException e) { System.out.println("Error creating class: "+e.getMessage()); } } } 成功装入驱动程序之后,应用程序可以连接到数据库。 DriverManager 可以找到驱动程序的第二种方法是对在 sql.drivers 系统特性表中发现的任何驱动程序进行循环。sql.drivers 特性表是由一个冒号分开的潜在驱动程序列表。在动态装入类之前总是先检查该列表,因此如果要使用特定驱动程序,请确保 sql.drivers 特性表或者为空或者以您期望的驱动程序开始。 创建连接 成功装入驱动程序之后,应用程序可以连接到数据库。 DriverManager 通过静态 getConnection() 方法进行连接,该方法以数据库的 URL 作为自变量。URL 通常被引用为: jdbc:<sub-protocol>:databasename 然而,还可以用活动驱动程序理解的任何格式编写引用 URL。有关 URL 的格式,请参考您的 JDBC 驱动程序文档。 涉及到子协议的一种情况是通过 ODBC 连接。如果直接通过 ODBC 访问 DSN 为 pricing 的样本数据库,则 URL 可能是: odbc:pricing 这意味着要通过 JDBC 连接,URL 可能是: jdbc:odbc:pricing 下面创建了实际的连接: import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; public class Pricing extends Object { public static void main (String args[]){ //For the JDBC-ODBC bridge, use //driverName = "sun.jdbc.odbc.JdbcOdbcDriver" //and //connectURL = "jdbc:odbc:pricing" String driverName = "JData2_0.sql.$Driver"; String connectURL = "jdbc:JDataConnect://127.0.0.1/pricing"; Connection db = null; try { Class.forName(driverName); db = DriverManager.getConnection(connectURL); } catch (ClassNotFoundException e) { System.out.println("Error creating class: "+e.getMessage()); } catch (SQLException e) { System.out.println("Error creating connection: "+e.getMessage()); } } } 一旦成功创建了连接,就可以执行任何需要的数据库操作(例如,插入或更新数据)。 关闭连接 因为 Statement 和 Connection 是对象,所以 Java 将对它们进行垃圾收集处理,释放它们占用的数据库资源。这可能会使您误认为无需去关闭这些对象,但实际不是这样。 Java 应用程序本身拥有大量可用资源是完全有可能的,这意味着很少进行垃圾收集。还有一种可能,虽然 Java 应用程序拥有大量资源,但可用的数据库资源却是有限的。很多数据库资源都可能由 Java 对象占用,而应用程序是本可以轻易关闭这些对象的。 不管是否有错误,确保关闭这些对象很重要,因此要向已有的 try-catch 块添加一个 finally 块: ... Connection db = null; try { Class.forName(driverName); db = DriverManager.getConnection(connectURL); } catch (ClassNotFoundException e) { System.out.println("Error creating class: "+e.getMessage()); } catch (SQLException e) { System.out.println("Error creating connection: "+e.getMessage()); } finally { System.out.println("Closing connections..."); try { db.close(); } catch (SQLException e) { System.out.println("Can't close connection."); } } } } 令人啼笑皆非的是,close() 方法本身可以抛出一个 SQLException,因此它需要自己的 try-catch 块。 SELECT 语句剖析 一旦连接到数据库,应用程序就可以开始检索数据了。在 SQL 数据库中,通常使用 SELECT 语句检索数据。SELECT 语句有几个基本部分。例如: SELECT product_id, product_name FROM products WHERE product_id < 1000 ORDER BY product_id 可以如下解释该语句: • SELECT product_id, product_name:select 字句,它定义将返回表(或一组表)中的哪些列。要返回所有列,请使用 SELECT *。 • FROM products:from 字句,它定义包含要返回数据的表。可以从多个表中选择。这也称为联接(join),并且需要非常巧妙的 where 子句。 • WHERE product_id < 1000:where 子句,它确定将返回哪些可用行。 • ORDER BY product_id:order by 子句,它确定返回数据的顺序。 SELECT 语句还将多个行组合在一起并返回一个聚合值。例如,下面的语句返回在 2001 年 9 月 15 日创收超过 1000 美元的所有产品的总收入: SELECT product_id, sum(quantity*price) as revenue FROM orders WHERE order_date = '9/15/01' GROUP BY product_id HAVING revenue > 1000 SELECT 语句由 Statement 对象执行。 创建 statement 创建 Statement 对象很简单;只需使用 Connection 的 createStatement() 方法即可,务必要捕获可能产生的 SQLException。本节中使用的大多数类都抛出 SQLException,因此,余下的代码都在这个 try-catch 块中。 ... import java.sql.Statement; public class Pricing extends Object { ... } catch (SQLException e) { System.out.println("Error creating connection: "+e.getMessage()); } //Create the Statement object, used to execute the SQL statement Statement statement = null; try { statement = db.createStatement(); } catch (SQLException e) { System.out.println("SQL Error: "+e.getMessage()); } finally { System.out.println("Closing connections..."); try { db.close(); } catch (SQLException e) { System.out.println("Can't close connection."); } } } } 这类似于创建 PreparedStatement 和用 CallableStatement 调用存储过程。 执行 statement 要实际检索数据,必须执行 Statement。这通常需要传递一个 SELECT 语句,该语句创建一组以 ResultSet 返回的数据,如下所示。 ... import java.sql.ResultSet; public class Pricing extends Object { ... //Create the Statement object, used to execute the SQL statement Statement statement = null; //Create the ResultSet object, which ultimately holds the data retrieved ResultSet resultset = null; try { statement = db.createStatement(); //Execute the query to populate the ResultSet resultset = statement.executeQuery("SELECT * FROM products"); } catch (SQLException e) { System.out.println("SQL Error: "+e.getMessage()); } finally { ... 如果 SQL 语句存在问题(例如,引用不存在的表),则应用程序抛出 SQLException。否则,不管是否发现数据,应用程序继续执行。 测试数据 ResultSet 被创建之后,它就有一个引用数据集内相对位置的“指针”。在 ResultSet 语句返回之后(即使表为空),该指针正好位于第一行的“上面”。 要到达实际数据的第一行,应用程序调用 next() 方法。该方法返回一个 Boolean 值,指出在新位置处是否有行存在。如果没发现数据,则 next() 返回 false。 ... resultset = statement.executeQuery("select * from products"); //Check for data by moving the cursor to the first record (if there is one) if (resultset.next()) { System.out.println("Data exists."); } else { System.out.println("No data exists."); } } catch (SQLException e) { ... 在稍后的循环处理数据中将使用类似的技术。 数据类型 一旦确定存在数据,就可以使用 ResultSet 的 getXXX() 方法检索数据。实际上没有名为 getXXX() 的方法,但却有一组以这种方式引用的方法,因为 ResultSet 可以以多种类型返回数据。例如,如果数据单元是数据库中一个整数,则 ResultSet 可以将它作为数字(用 getDouble()、getInt() 和 getFloat() 等方法)、作为 String(用 getString())或作为数组(用 getArray())返回给应用程序。甚至还可以将多个单元的数据作为 InputStream(用 getBinaryStream())或 Reader(用 getCharacterStream())检索。 要记住的重要事情是:Java 可以非常接近地匹配在大多数数据库中发现的数据类型,并且仅有特定的数据类型可以作为不同的类型来检索。例如,getString() 可以检索以任何数据库类型初始化的数据,但是只能用 getDate()(或 getTime())、getObject() 或 getString() 检索日期和时间值。 通过名称检索数据 可以用两种方法检索数据本身:通过名称和通过索引。要通过名称检索,请使用前面讨论过的 getXXX() 方法之一。这些方法采用 int 或 String 形式的自变量。一会将详细讲述通过索引检索数据;现在,应用程序将通过列名检索字段。 最终,所有这些数据都将包括在一个 XML 文件中,而它们在 XML 文件中将是文本,因此请对所有的值使用 getString(): ... if (resultset.next()) { //Output data by referencing the ResultSet columns by name System.out.print(resultset.getString("product_id")+"|"); System.out.print(resultset.getString("product_name")+" | "); System.out.print(resultset.getString("base_price")+" | "); System.out.print(resultset.getString("size")+" | "); System.out.print(resultset.getString("unit")+" | "); System.out.print(resultset.getString("lower")+" | "); System.out.print(resultset.getString("upper")+" | "); System.out.println(resultset.getString("unit_price")); } else { System.out.println("No data exists."); } ... 编译和运行该应用程序显示了第一行数据 — 在线美食零售店的批量定价信息。 循环处理数据 每次显示一行很有用,但是更好的方法是循环处理数据,并且将每个记录显示在单独一行中。 应用程序通过前进到下一行并输出数据、然后再前进到下一行来实现这点。当指针越过了最后一行时,next() 返回 false,并且循环结束。 ... resultset = statement.executeQuery("select * from products"); //Execute the loop as long as there is a next record while (resultset.next()) { //Output data by referencing the ResultSet columns by name System.out.print(resultset.getString("product_id")+" | "); System.out.print(resultset.getString("product_name")+" | "); System.out.print(resultset.getString("base_price")+" | "); System.out.print(resultset.getString("size")+" | "); System.out.print(resultset.getString("unit")+" | "); System.out.print(resultset.getString("lower")+" | "); System.out.print(resultset.getString("upper")+" | "); System.out.println(resultset.getString("unit_price")); } } catch (SQLException e) { ... 运行这个应用程序将返回表中的所有行。 通过索引检索数据 当然,通过列名检索数据可能相当不方便,特别是存在很多列的时候。它还使一般化和定制检索变得困难。要以更快和更易定制的方式检索数据,请使用每列的索引号。 第一列的索引为 1,第二列为 2,依此类推。我们知道存在 8 列,因此可以如下重新编写前一屏中的示例: ... while (resultset.next()) { for (int i=1; i <= 8; i++) { //Output each column by its index System.out.print(resultset.getString(i)+" | "); } //Output a line feed at the end of the row System.out.println(""); } ... 使用上述技术使得数据检索过程完全通用了,因而也可以移植到其它类似的任务和/或应用程序中。 通用检索 本教程的最终目标是创建一个基于完全独立于应用程序的映射文件的数据检索和操纵过程。因此,应用程序需要在不知道数据的结构的前提下检索数据的能力。换句话说,应用程序需要一种方法来独立地查明 ResultSet 中有多少列。 要做到这点,请使用 ResultSetMetaData 类。象数据库元数据一样,它可以访问每一列的列号和类型,以及列名和其它信息。 可以使用 ResultSetMetaData 输出有关某个记录的全部信息,即使该记录只具有 ResultSet 功能本身也是如此。 ... import java.sql.ResultSetMetaData; public class Pricing extends Object { ... Statement statement = null; ResultSet resultset = null; //Create the ResultSetMetaData object, which will hold information about //the ResultSet ResultSetMetaData resultmetadata = null; try { statement = db.createStatement(); resultset = statement.executeQuery("select * from products"); //Get the ResultSet information resultmetadata = resultset.getMetaData(); //Determine the number of columns in the ResultSet int numCols = resultmetadata.getColumnCount(); while (resultset.next()) { for (int i=1; i <= numCols; i++) { //For each column index, determine the column name String colName = resultmetadata.getColumnName(i); //Get the column value String colVal = resultset.getString(i); //Output the name and value System.out.println(colName+"="+colVal); } //Output a line feed at the end of the row System.out.println(" "); } ... 现在运行该应用程序不仅给出数据,而且还给出列名,在您为数据创建 XML 文档时将需要列名。 ResultSet 选项 到目前为止,应用程序使用了用缺省特性创建的 ResultSet,但是,可以调整这些特性以控制一些事情,例如获取数据的方向以及 ResultSet 是否显示其他用户所作的更改。 例如,可以用如下方式创建 ResultSet:可以滚动或使用它来直接更新数据。它从上至下获取数据,并且不受其他数据库用户所作更改的影响。 这些特性实际在最终创建 ResultSet 的 Statement 上设置: Statement statement = db.createStatement( ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.FETCH_FORWARD ); 请注意,向 ResultSet 方法传递多个定制特性可以影响应用程序的性能。 PreparedStatement 一种提高应用程序性能的方法是使用 PreparedStatement。 除了由 SQL 语句以参数的形式创建以外,PreparedStatement 类似于 Statement。在大多数情况下,数据库将预编译(或者至少高速缓存)SQL 语句。例如,如果如下组织应用程序,则将预编译 SELECT 语句: ... import java.sql.PreparedStatement; public class Pricing extends Object { ... //Create the PreparedStatement PreparedStatement statement = null; ResultSet resultset = null; ResultSetMetaData resultmetadata = null; try { //Compile or cache the SQL within the database and prepare it for execution statement = db.prepareStatement("select * from products"); //Execute the SQL above to populate the ResultSet resultset = statement.executeQuery(); //Get the ResultSet Information resultmetadata = resultset.getMetaData(); ... 当要反复执行某一特定查询时,PreparedStatement 可能最有用。在这种情况下,能够在运行时设置参数是很有用的。 设置 IN 参数 在大多数将重用 Statement 或 PreparedStatement 的情况下,每一次使用都会略有不同,例如记录的范围不同。在这些情况下,您需要 IN 参数。例如,为检索要在运行时确定的一系列 product_id,请使用: ... statement = db.prepareStatement("select * from products where "+ "product_id < ? and product_id > ?"); statement.setInt(1, 5); statement.setInt(2, 10); resultset = statement.executeQuery(); ... setXXX() 方法与 getXXX() 方法类似,唯一差别在于它们包括参数号和要为那个参数设置的值。例如,setInt(2, 10) 用整数 10 替换语句中的第二个 ?,这样该语句被当成以下代码来执行: select * from products where product_id < 5 and product_id > 10 在用 CallableStatement 调用存储过程时也可以使用类似的技术。 用 CallableStatement 调用存储过程 大多数现代数据库允许开发人员在数据库内部创建存储过程。这种存储过程可以象一条 SQL 语句那样简单,也可以象一个小型应用程序那样复杂。不管在哪种情况下,有时有必要从 Java 调用这些过程以产生一组要检索的数据。 CallableStatement 类继承 PreparedStatement 并允许开发人员为数据库查询指定参数。然后,CallableStatement 返回一个或多个 ResultSet。 创建 CallableStatement 与创建 PreparedStatement 非常类似,其不同之处在于使用调用语句而不是 SQL 语句。然后,由驱动程序将调用语句转换成本地调用。 statement = db.prepareCall("{call product_listing }"); 注: CallableStatement 和 PreparedStatement 之间的一个区别是:除了通常创建的 ResultSet 之外,CallableStatement 还可以提供 OUT 参数。 检测空值 本教程中的数据涉及到项目的批量定价。正因为这点,一些数量范围的上限为空,表明不能有更多的批量折扣。 这当然不错,但是它使最终 XML 文档的构建变得困难,因为空值可以导致问题。要解决此问题,可以使用 wasNull() 方法来确定是否有特定的数据片为空,如果为空,则用其它值替换它。下面这个示例演示了如何用字“and up”替换空值。 ... while (resultset.next()) { //Output each row for (int i=1; i <= numCols; i++) { //For each column, get name and value information String colName = resultmetadata.getColumnName(i); String colVal = resultset.getString(i); //Determine if the last column accessed was null if (resultset.wasNull()) { colVal = "and up"; } //Output the information System.out.println(colName+"="+colVal); } System.out.println(" "); } ... 请注意,wasNull() 没有自变量。该方法作用于从 ResultSet 检索的最后一列,因此,必须首先调用 getXXX()。 这时,应用程序正在检索合适的数据和列名。您已经可以开始构建 XML 文档了。 设置映射 映射的工作原理 本教程的目标是演示如何使用映射文件并利用从数据库中抽取的数据来创建 XML 文档。换句话说,映射文件确定检索什么数据以及最终如何在 XML 文件中表示该数据。 这样做的一种方法是在从数据库抽取数据时用这些数据创建一个临时 XML 文档,然后将该数据改成符合映射文件的新格式。映射文件确定抽取什么数据、新文件的名称和结构以及在何处存储什么数据。 结构 映射文件包含几种信息: • data 元素形式的初始数据。为得到最大的灵活性,这是 SQL 语句的形式。通过这种方式,您可以使用映射文件来指定应该从多个表抽取数据。 • 新文档的整体结构。 这采用 root 元素的形式,它通过属性指定目标根元素名和要表示数据库行的元素名。 • 数据元素的名称和内容。 这些内容包含在 element 元素系列中。element 包括新元素名称和 attribute 或 content 元素。这两种元素指出应该添加的数据,在表示属性时,还指出属性应该采用的名称。例如,如果 description 元素应该有一个表示 product_id 列的 product_number 属性并以 product_name 作为内容,则映射文件应该将它表示成: <element name="description"> <attribute name="product_number">product_id</attribute> <content>product_name</content> </element> 映射文件 最终的映射文件如下: <?xml version="1.0"?> <mapping> <data sql="select * from products" /> <root name="pricingInfo" rowName="product"> <element name="description"> <attribute name="product_number">product_id</attribute> <content>product_name</content> </element> <element name="quantity"> <content>lower</content> </element> <element name="size"> <content>size</content> </element> <element name="sizeUnit"> <content>unit</content> </element> <element name="quantityPrice"> <content>unit_price</content> </element> </root> </mapping> 用SQL创建xml 文档 算法 创建新 XML 文档的过程如下: 1. 解析映射文件以使包括要检索的数据在内的必要信息可用。 2. 检索源查询。这允许基于映射文件动态检索数据。 3. 将数据存储在 Document 对象中。然后就可以从这个临时文档抽取数据以根据映射创建目标文档。 4. 检索数据映射以使它可用于应用程序。 5. 循环处理初始数据。分析每一行数据并将其重新映射到新结构中。 6. 检索元素映射。映射文件确定以什么样的顺序从临时文档中抽取什么数据。 7. 向新文档添加元素。一旦检索了数据,就用新名称将它添加到新文档中。 8. 向新文档中添加属性。最后,向适当的元素中添加属性。 解析映射文件 创建新文档的第一步是检索映射,这只能通过解析映射文件来完成。请注意,因为此文件还包含对最终将检索的数据的引用,因此必须在能够执行任何数据库操作之前解析它。 ... import org.w3c.dom.Document; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; public class Pricing extends Object { public static void main (String args[]){ //Create the Document object Document mapDoc = null; try { //Create the DocumentBuilderFactory DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); //Create the DocumentBuilder DocumentBuilder docbuilder = dbfactory.newDocumentBuilder(); //Parse the file to create the Document mapDoc = docbuilder.parse("mapping.xml"); } catch (Exception e) { System.out.println("Problem creating document: "+e.getMessage()); } //For the JDBC-ODBC bridge, use //driverName = "sun.jdbc.odbc.JdbcOdbcDriver" //and //connectURL = "jdbc:odbc:pricing" String driverName = "JData2_0.sql.$Driver"; String connectURL = "jdbc:JDataConnect://127.0.0.1/pricing"; Connection db = null; ... 检索源查询 下一步,检索存储在 data 元素的 sql 属性中的源查询。 ... import org.w3c.dom.Element; import org.w3c.dom.Node; ... System.out.println("Problem creating document: "+e.getMessage()); } //Retrieve the root element Element mapRoot = mapDoc.getDocumentElement(); //Retrieve the (only) data element and cast it to Element Node dataNode = mapRoot.getElementsByTagName("data").item(0); Element dataElement = (Element)dataNode; //Retrieve the sql statement String sql = dataElement.getAttribute("sql"); //Output the SQL statement System.out.println(sql); //For the JDBC-ODBC bridge, use //driverName = "sun.jdbc.odbc.JdbcOdbcDriver" //and //connectURL = "jdbc:odbc:pricing" String driverName = "JData2_0.sql.$Driver"; String connectURL = "jdbc:JDataConnect://127.0.0.1/pricing"; Connection db = null; ... 首先,确定根元素,然后检索 data 节点。因为只有一个 data 元素,所以可以直接检索它。还可以利用类似的技术从依次运行的几个查询构建文档。 最终,将 Node 的类型强制转换成 Element,以便可以使用 Attribute 值。 除去前面的输出语句,然后运行应用程序以将 SQL 语句作为输出显示。 将数据存储在 Document 对象中 一旦成功地从数据库抽取了数据,该数据就被存储在临时的document中。通用方法是为每一行数据创建一个 row 元素,将每一列表示成一个以该列命名的元素,将数据本身作为该元素的内容。 ... public static void main (String args[]){ Document mapDoc = null; //Define a new Document object Document dataDoc = null; try { //Create the DocumentBuilderFactory DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); //Create the DocumentBuilder DocumentBuilder docbuilder = dbfactory.newDocumentBuilder(); //Parse the file to create the Document mapDoc = docbuilder.parse("mapping.xml"); //Instantiate a new Document object dataDoc = docbuilder.newDocument(); } catch (Exception e) { System.out.println("Problem creating document: "+e.getMessage()); } ... ResultSetMetaData resultmetadata = null; //Create a new element called "data" Element dataRoot = dataDoc.createElement("data"); try { statement = db.createStatement(); resultset = statement.executeQuery("select * from products"); resultmetadata = resultset.getMetaData(); int numCols = resultmetadata.getColumnCount(); while (resultset.next()) { //For each row of data //Create a new element called "row" Element rowEl = dataDoc.createElement("row"); for (int i=1; i <= numCols; i++) { //For each column, retrieve the name and data String colName = resultmetadata.getColumnName(i); String colVal = resultset.getString(i); //If there was no data, add "and up" if (resultset.wasNull()) { colVal = "and up"; } //Create a new element with the same name as the column Element dataEl = dataDoc.createElement(colName); //Add the data to the new element dataEl.appendChild(dataDoc.createTextNode(colVal)); //Add the new element to the row rowEl.appendChild(dataEl); } //Add the row to the root element dataRoot.appendChild(rowEl); } } catch (SQLException e) { System.out.println("SQL Error: "+e.getMessage()); } finally { System.out.println("Closing connections..."); try { db.close(); } catch (SQLException e) { System.out.println("Can't close connection."); } } //Add the root element to the document dataDoc.appendChild(dataRoot); } } 特别在上面的代码示例中,首先与根元素 data 一起创建一个空文档。对于数据库中的每一行,创建一个 row 元素,然后对于每一个列,创建一个单独的元素并将它添加到该行中。最后,将每一个 row 元素添加到根元素中,并将根元素添加到document 中。 检索数据映射 一旦拥有数据,就应该将它映射成新结构:从已解析映射文档检索映射。首先,检索有关根元素和行元素的信息,然后检索元素映射本身。 ... import org.w3c.dom.NodeList; ... dataDoc.appendChild(dataRoot); //Retrieve the root element (also called "root") Element newRootInfo = (Element)mapRoot.getElementsByTagName("root").item(0); //Retrieve the root and row information String newRootName = newRootInfo.getAttribute("name"); String newRowName = newRootInfo.getAttribute("rowName"); //Retrieve information on elements to be built in the new document NodeList newNodesMap = mapRoot.getElementsByTagName("element"); } } 有了这个信息,就可以构建新的 Document。 循环处理初始数据 每一个初始行都以 row 元素的形式存储在临时文档中。您将要循环处理这些元素,因此将它们作为 NodeList 检索。 ... NodeList newNodesMap = mapRoot.getElementsByTagName("element"); //Retrieve all rows in the old document NodeList oldRows = dataRoot.getElementsByTagName("row"); for (int i=0; i < oldRows.getLength(); i++){ //Retrieve each row in turn Element thisRow = (Element)oldRows.item(i); } ... 检索元素映射 既然您有了数据和映射信息,就应该开始构建新的 Document。对于每一行,循环处理映射,以便确定应该从临时 Document 检索数据列的顺序,以及确定它们被添加到新 Document 中时的名称。 ... for (int i=0; i < oldRows.getLength(); i++){ //Retrieve each row in turn Element thisRow = (Element)oldRows.item(i); for (int j=0; j < newNodesMap.getLength(); j++) { //For each node in the new mapping, retrieve the information //First the new information... Element thisElement = (Element)newNodesMap.item(j); String newElementName = thisElement.getAttribute("name"); //Then the old information Element oldElement = (Element)thisElement.getElementsByTagName("content").item(0); String oldField = oldElement.getFirstChild().getNodeValue(); } } ... 对于 newNodesMap 中的每一个元素,应用程序都检索新的元素名,然后是要检索的旧元素名。 向新文档添加元素 向文档中添加新元素很简单:其实就是用正确的名称创建一个新元素,然后检索合适的数据并将该数据设置成该元素的内容。 ... public static void main (String args[]){ Document mapDoc = null; Document dataDoc = null; //Create the new Document Document newDoc = null; try { //Create the DocumentBuilderFactory DocumentBuilderFactory dbfactory = DocumentBuilderFactory.newInstance(); //Create the DocumentBuilder DocumentBuilder docbuilder = dbfactory.newDocumentBuilder(); //Parse the file to create the Document mapDoc = docbuilder.parse("mapping.xml"); //Instantiate a new Document object dataDoc = docbuilder.newDocument(); //Instantiate the new Document newDoc = docbuilder.newDocument(); } catch (Exception e) { System.out.println("Problem creating document: "+e.getMessage()); } ... //Retrieve the root element (also called "root") Element newRootInfo = (Element)mapRoot.getElementsByTagName("root").item(0); //Retrieve the root and row information String newRootName = newRootInfo.getAttribute("name"); String newRowName = newRootInfo.getAttribute("rowName"); //Retrieve information on elements to be built in the new document NodeList newNodesMap = mapRoot.getElementsByTagName("element"); //Create the final root element with the name from the mapping file Element newRootElement = newDoc.createElement(newRootName); NodeList oldRows = dataRoot.getElementsByTagName("row"); for (int i=0; i < oldRows.getLength(); i++){ //For each of the original rows Element thisRow = (Element)oldRows.item(i); //Create the new row Element newRow = newDoc.createElement(newRowName); for (int j=0; j < newNodesMap.getLength(); j++) { //Get the mapping information for each column Element thisElement = (Element)newNodesMap.item(j); String newElementName = thisElement.getAttribute("name"); Element oldElement = (Element)thisElement.getElementsByTagName("content").item(0); String oldField = oldElement.getFirstChild().getNodeValue(); //Get the original values based on the mapping information Element oldValueElement = (Element)thisRow.getElementsByTagName(oldField).item(0); String oldValue = oldValueElement.getFirstChild().getNodeValue(); //Create the new element Element newElement = newDoc.createElement(newElementName); newElement.appendChild(newDoc.createTextNode(oldValue)); //Add the new element to the new row newRow.appendChild(newElement); } //Add the new row to the root newRootElement.appendChild(newRow); } //Add the new root to the document newDoc.appendChild(newRootElement); } } 首先,创建新的 Document,然后创建新的根元素,该元素的名称从映射信息获得。对于临时 Document 中的每一行,使用映射中指定的 newRowName 为新行创建一个元素。 对于每一行,循环处理在映射中指定的每一个新元素,然后检索初始数据。在前一屏中,您按顺序检索了 content 元素的文本。使用此信息确定按什么顺序从临时行中检索哪些节点。一旦有了旧数据和新名称,就创建新元素并将它添加到该行中。 最后,将新行添加到根元素,然后将根元素添加到 Document。唯一尚未添加的就是属性了。 向新文档中添加属性 新的 Document 几乎完成。您已经添加了新元素,但是还没有添加本可以指定的属性。添加属性类似于添加新元素本身。然而,一个元素可能有多个属性,因此必须在代码中解决这个问题。完成这个任务的最简单方法是将 attribute 元素检索到 NodeList 中,然后迭代该列表以处理每个元素。对于每一个期望的属性,从初始 Document 确定字段名称,然后确定新 Document 中的名称。 ... Element newElement = newDoc.createElement(newElementName); newElement.appendChild(newDoc.createTextNode(oldValue)); //Retrieve list of new elements NodeList newAttributes = thisElement.getElementsByTagName("attribute"); for (int k=0; k < newAttributes.getLength(); k++) { //For each new attribute //Get the mapping information Element thisAttribute = (Element)newAttributes.item(k); String oldAttributeField = thisAttribute.getFirstChild().getNodeValue(); String newAttributeName = thisAttribute.getAttribute("name"); //Get the original value oldValueElement = (Element)thisRow.getElementsByTagName(oldAttributeField).item(0); String oldAttributeValue = oldValueElement.getFirstChild().getNodeValue(); //Create the new attribute newElement.setAttribute(newAttributeName, oldAttributeValue); } //Add the element to the new row newRow.appendChild(newElement); } //Add the new row to the root newRootElement.appendChild(newRow); ... 最终文档 在本过程最后,newDoc 中有新格式的旧信息。从现在开始,可以在另一个应用程序中使用该文档,也可以使用 XSLT 或其它方法进一步转换它。 <pricingInfo> <product> <description product_number="1">Filet Mignon</description> <quantity>1</quantity> <size>1</size> <sizeUnit>item</sizeUnit> <quantityPrice>40</quantityPrice> </product> <product> <description product_number="2">Filet Mignon</description> <quantity>11</quantity> <size>1</size> <sizeUnit>item</sizeUnit> <quantityPrice>30</quantityPrice> </product> <product> <description product_number="3">Filet Mignon</description> <quantity>101</quantity> <size>1</size> <sizeUnit>item</sizeUnit> <quantityPrice>20</quantityPrice> </product> <product> <description product_number="4">Prime Rib</description> <quantity>1</quantity> <size>1</size> <sizeUnit>lb</sizeUnit> <quantityPrice>20</quantityPrice> </product> <product> <description product_number="5">Prime Rib</description> <quantity>101</quantity> <size>1</size> <sizeUnit>lb</sizeUnit> <quantityPrice>15</quantityPrice> </product> ... [/size][/color]
|