在JDBC4.0推出後,它的從多的特性正在受到廣泛地關注。而最重要的更新就是支持XML數據類型(在最新的SQL2003標准中定義了這種數據類型),當然,將XML數據保存在數據庫中,並在應用程序中更新XML數據並不是什麼新技術。但這是JDBC第一次提供了一個映射接口(java.sql.SQLXML),並利用這個接口來支持SQL/XML數據類型。當然,為了滿足處理XML數據類型的需要,其他的接口,如java.sql.Connection和java.sql.ResultSet,也被更新了。
在SQL2003標准和XML數據類型推出之前,開發人員必須將XML數據保存在BLOB、CLOB或TEXT類型字段中。現在,很多主流的數據庫(如SQL Server、Oracle和DB2)已經加入了對XML數據類型的支持。但在JDBC4以前,Java應用程序仍然必須將數據庫中的XML數據類型轉換為JDBC支持的數據類型。但新的JDBC可以通過本地的接口來綁定XML,因此,在處理任何數據庫中的XML數據變得更容易和高效。
在本文中將介紹如何用JDBC4.0來操作XML類型的字段(保存和獲得XML數據),並給出了一個例子供讀者參考。
一、存儲和獲得XML數據
為了將XML數據保存在一個XML類型的字段中,我們首先應該調用java.sql.Connection.createSQLXML()方法。這個方法返回了一個java.sql.SQLXML的實例。然後我們可以通過調用setOutputStream(), setCharacterStream()或簡單地調用setString(String xml)來將XML數據加到SQLXML對象中。要注意的這個功能非常類似於BLOB和CLOB類型的使用。
JDBC4.0的關鍵特性之一就是我們還可以通過調用java.sql.SQLXML的setResult(Class resultClass)方法來獲得一個設置avax.xml.transform.Result的類的實現。這些類包括DOMResult, JAXBResult以及SAXResult。換句話說,我們無需轉換就可以簡單地做以下事情:
1.得到XML數據
2.建立一個獨立的DOMResult對象
3.將DOMResult傳入java.sql.SQLXML對象
4.直接通過java.sql.Statement將XML數據保存到響應數據庫字段中
為了java.sql.ResultSet獲得SQLXML類型數據,我們只需要地調用getSQLXML,並指定相應地字段名或索引即可。然後我們可以通過getBinaryStream(), getCharacterStream()或getString()從java.io.InputStream中獲得實際的XML數據,或是一個簡單的字符串。保存XML數據也獲得XML數據的過程類似,我們還可以通過調用SQLXML對象實例的getSource(Class sourceClass)方法來獲得XML源,因此,我們可以從任何實現javax.xml.transform.Source的類訪問XML數據。
二、實例程序
由於JDBC4是在2006年12月11日由官方發布的(隨J2SE6.0發布),因此,現在很多數據庫驅動對JDBC4支持的還不是很好。在本例子中使用了Apache Derby數據庫的較版本10.2來討論對XML類型數據的保存和獲取。Derby的這個版本還不持java.sql.SQLXML,這就意味著我們不能直接從結果值中獲得XML數據,以及綁定XML數據。但Derby和SQL 2003兼容,可以常非容易地使用嵌入模式,類此,它仍然可以為我們演示如何操作XML數據,就好象在使用一個完全支持JDBC4的驅動一樣。用於操作Derby的XML數據的代碼如下:
import java.io.StringReader; import java.sql.*; public class XmlDbTester { static final String XML1 = "<article>"+ "<title>First Article</title>"+ "<author>John Smith</author>"+ "<body>A very short article.</body>"+ "</article>"; static final String XML2 = "<article>"+ "<title>Second Article</title>"+ "<author>Mary Jones</author>"+ "<body>Another short article.</body>"+ "</article>"; static final String XML3 = "<article>"+ "<title>Third Article</title>"+ "<author>John Smith</author>"+ "<body>Last short article.</body>"+ "</article>"; static final String[] ARTICLES = {XML1, XML2, XML3}; public static void main(String s[]) { XmlDbTester xdt = new XmlDbTester(); Connection c = xdt.getConnection(); xdt.loadDemoData(c); xdt.demoXmlResult(c); xdt.demoXPath(c); xdt.closeConnection(c); System.out.println("Done"); System.exit(0); } void demoXmlResult(Connection c) { try { Statement s = c.createStatement(); ResultSet rs = s.executeQuery("SELECT XMLSERIALIZE (DATA AS CLOB) "+ "FROM ARTICLE WHERE ID = 2"); while(rs.next()) System.out.println("The article XML for article with ID = 2: \n"+ rs.getString(1)); s.close(); rs.close(); } catch(Exception e) { e.printStackTrace(); } } void demoXPath(Connection c) { try { Statement s = c.createStatement(); ResultSet rs = s.executeQuery("SELECT ID FROM ARTICLE WHERE "+ "XMLEXISTS('//author[text()=\"John Smith\"]' PASSING BY REF "+ "DATA)"); while(rs.next()) System.out.println("John Smith wrote article with ID: "+ rs.getInt(1)); s.close(); rs.close(); } catch(Exception e) { e.printStackTrace(); } } void loadDemoData(Connection c) { try { Statement s = c.createStatement(); s.execute("CREATE TABLE ARTICLE(ID INTEGER, DATA XML)"); System.out.println("Created demo table: ARTICLE"); s.close(); PreparedStatement ps = null; int id = 1; for(String insert : ARTICLES) { ps = c.prepareStatement("INSERT INTO ARTICLE (ID, DATA) VALUES "+ "(?, XMLPARSE (DOCUMENT CAST (? AS CLOB) PRESERVE "+ "WHITESPACE))"); ps.setInt(1, id++); ps.setClob(2, new StringReader(insert)); ps.executeUpdate(); } System.out.println("Inserted test data into ARTICLE"); if(ps != null ) ps.close(); } catch(SQLException e) { e.printStackTrace(); } } Connection getConnection() { Connection c = null; try { c = DriverManager.getConnection("jdbc:derby:XmlDemo;create=true"); c.setAutoCommit(false); } catch (Exception e) { e.printStackTrace(); } return c; } void closeConnection(Connection c) { try { c.close(); } catch(Exception e) {} } }
Derby數據庫中存在很多專門針對XML的操作符,如XMLPARSE和XMLSERIALIZE,它們將幫助我們將數據轉換為字符數據流或字符串,以便我們在程序中使用。在調用這些語句之後,將是我們例子中的具體要完成的任務。在這裡,我還會介紹如使用和SQL/XML完全兼容的JDBC驅動來完成這些任務。事實上,我們可以完全使用java.sql.SQLXML來實現這個例子,這樣做雖然會編譯通過,但程序運行的結果會拋出一個Derby-specific異常,如"Binding directly to an XML value is not allowed."。總之,本例的代碼只是為了演示程序如何也SQL/XML兼容性數據庫交互。下面我將分別講解一下重要的代碼片段。
在上面的例子代碼中,首先建立一個簡單的數據表,這個數據表包含一個XML類型的數據字段DATA,代碼如下:
Statement s = c.createStatement();
s.execute("CREATE TABLE ARTICLE(ID INTEGER, DATA XML)");
接下來向數據表中插入數據。由於目前Derby還不支持java.sql.SQLXML類型的數據,當我們在DATA插入數據時,必須將XML數據綁字到其它可以析XML數據的類型上。在這裡我們使用CLOB類型,代碼如下:
ps = c.prepareStatement("INSERT INTO ARTICLE (ID, DATA) VALUES "+
"(?, XMLPARSE (DOCUMENT CAST (? AS CLOB) PRESERVE "+
"WHITESPACE))");
ps.setInt(1, id++);
ps.setClob(2, new StringReader(insert));
現在,如果我們有一個和JDBC4完全兼容的驅動,也可以使用java.io.Writer來做同樣的事,代碼如下:
ps = c.prepareStatement("INSERT INTO ARTICLE (ID, DATA) values (?, ?)");
SQLXML article = c.createSQLXML();
Writer writer = article.setCharacterStream();
writer.write(insert);
writer.close();
ps.setInt(1, id++);
ps.setSQLXML(2, article);
或使用javax.xml.transform.dom.DOMSource來完成這項工作:
ps = c.prepareStatement("INSERT INTO ARTICLE (ID, DATA) values (?, ?)");
SQLXML article = c.createSQLXML();
DOMResult dom = (DOMResult)article.setResult(DOMResult.class);
dom.setNode(doc); // doc is instance of org.w3c.dom.Document
ps.setInt(1, id++);
ps.setSQLXML(2, article);
接下來我們來從數據庫中獲得XML數據。和插入數據類似,當我們從Derby中獲得XML數據時,必須將XML數據類型轉換成字符類型,代碼如下:
ResultSet rs = s.executeQuery("SELECT XMLSERIALIZE (DATA AS CLOB) "+
"FROM ARTICLE WHERE ID = 2");
如果使用支持java.sql.SQLXML的驅動,我們可以通過直接使用XML數據庫類型來完成類似的工作。也就是說我們可以直接得到XML數據。下面的代碼假設我們使用DOM分析XML,並獲得記錄集:
PreparedStatement st = c.prepareStatement("SELECT ID, DATA FROM ARTICLE");
ResultSet rs = st.executeQuery();
while (rs.next())
{
SQLXML article = rs.getSQLXML("DATA");
InputStream stream = article.getBinaryStream();
DocumentBuilder parser =
DocumentBuilderFactory.newInstance().newDocumentBuilder();
Document doc = parser.parse(stream);
// Do something...
}
在這種情況下無需使用getBinaryStream()方法,而取而代之的是getSource(Class sourceClass)方法,這個方法可以得到DOMSource、SAXSource或任何其他實現javax.xml.transform.Source的類實例。
下面我們來使用XPath來過濾XML數據。在這個例子的最後演示了如何在新版的SQL2003中使用XPath語句:XMLEXISTS來過濾數據。代碼如下:
Statement s = c.createStatement();ResultSet rs = s.executeQuery("SELECT ID FROM ARTICLE WHERE "+ "XMLEXISTS('//author[text()=\"John Smith\"]' PASSING BY REF "+ "DATA)");
注意,我們還可以使用XMLQUERY函數來執行XQuery表達式。由於XMLEXISTS和XMLQUERY函數所涉及的內容已經超出了本文的范疇,因此,本文並不涉及這些內容,感興趣的讀者可以參考其他的相關文檔。
三、更廣泛地應用
使數據庫支持SQL/XML可以改善代碼的透明度,也可以大幅度地縮短開發周期。在一般情況下,也許都會有一些不得以的原因將XML數據保存在數據庫中。這些原因也許是使用最通常的方法來演示在線媒體數據,如文檔、事件列表,或產品信息,為了將實體數據保存成XML,並將這些XML數據轉換成可以顯示的形式。java.sql.SQLXML API文檔為我們提供了很多小例子來演示如何做這些工作,有些例子也在Java SE6的文檔中列出,如下面的代碼:
File xsltFile = new File("transformer.xslt");
File xhtmlFile = new File("xhtml.xml");
Transformer xslt =
TransformerFactory.newInstance().newTransformer(new
StreamSource(xsltFile));
Source source = sqlxml.getSource(null);
Result result = new StreamResult(xhtmlFile);
xslt.transform(source, result);
上面代碼的sqlxml變量是一個從數據庫中獲得的java.sql.SQLXML實例。本質上,我們可以將XML數據轉換為XHTML形式,上面的幾行簡單的代碼完成了這項工作。