將一個JDBC的ResultSet轉成XML並輸出到文件


一·、需求

寫一個功能類,能將一個給定的sql select語句的執行結果集按照一定格式生成xml文件。

比如,一個sql語句"select * from star;"的執行結果是這樣的:

---------------------------

name     age   gender 

---------------------------

James    26     male

Bryant   33     male

要求生成后的xml的根節點名叫"star"且每條數據使用一個<row>標簽來代表,就像下面的這樣:

<?xml version="1.0" encoding="UTF-8" standalone="no" ?>

<star xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">

<row>

<name>James</name>

<age>26</age>

<gender>male</gender>

</row>

<row>

<name>Bryant</name>

<age>33</age>

<gender>male</gender>

</row>

</star>

二、早期作法

早期分析這個需求的時候,很自然的將需求分成了兩塊功能來完成,一塊用來生成xml,另一塊用來將xml輸出到文件。

1、生成xml:

生成xml的方式的總體思路是,首先通過JDBC連接執行sql得到結果集,然后遍歷結果集將內容填充到一個

org.w3c.dom.Document對象,最后使用javax.xml.transform.Transformer將Document對象轉換成xml。

@Component("xmlMaker")
public class XmlMaker{
  private static DataSource datasource;
  private static final String XSDNS="http://ww.w3.org/2001/XMLSchema";
  private static final String ROW_ELEMENT_NAME="row";
private String content; @Resource(name
="mydatasource") public void setDataSource(DataSource dataSource){ this.dataSource=datasource; } public String generateXML(String sql, String rootElementName){ Connection con = null; PreparedStatement ps = null; ResultSet rs = null; String result = null; try{ con = dataSource.getConnection();
// 創建一個可滾動的只讀結果集,普通類型的結果集的游標無法自由上下移動 ps
= con.prepareStatement(sql,ResultSet.TYPE_SCROLL_INSENSITIVE,ResultSet.CONCUR_READ_ONLY); rs = ps.executeQuery(); result = makeXMLFromResultSet(rs,rootElementName); } catch(Exception e){ //todo }finally{ try{ if(null != rs){ rs.close(); } if(null != ps){ ps.close(); } if(null != con){ con.close(); } }catch(SQLException e){ //todo } }
return result; }
private String makeXMLFromResultSet(ResultSet rs,String rootElementName) throws Exception{ Document doc = resultSet2Dom(rs, rootElementName); String ret = null; StringWriter sw = new StringWriter(); Transformer t = null; try{ TransformerFactory tf = TransformerFactory.newInstance(); t = tf.newTransformer(); t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
t.setOutputProperty(OutputKeys.METHOD, "xml");
t.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
t.setOutputProperty(OutputKeys.INDENT, "yes");
      DOMSource domSource = new DOMSource(doc);
StreamResult sr = new StreamResult(sw);
transformer.transform(domSource,sr);
content = sw.toString();
     }catch(Exception e){
//todo
}finally{
doc = null;
try{
sw.close();
}catch(IOException e){
}
}
return content;
}

private Document resultSet2Dom(ResultSet rs,String rootElementName){
Document myDocument = null;
try{
myDocument = ((DocumentBuilderFactory.newInstance()).newDocumentBuilder()).newDocument();
}catch(ParserConfigurationException pce){
//todo
}
Element root = myDocument.createElement(rootElementName);
root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
myDocument.appendChild(root);
ResultSetMetaData rsmd = rs.getMetaData();
Element element,row;
String value;
if(rs.next()){
rs.previous();//使用rs.next()來判斷結果集是否有至少一條數據,如果有就將游標退回初始位置准備開始遍歷。
while(rs.isLast() == false){
rs.next();
row = myDocument.createElement(ROW_ELEMENT_NAME);
root.appendChild(row);
for(int i=1;i<=rsmd.getColumnCount();i++){
element = myDocument.createElement(rsmd.getColumnLabel(i).toLowerCase());
int columnType = rsmd.getColumnType();//此處得到列類型是方便對特殊類型數據的處理,比如當數據是浮點型時四舍五入。本例略
value = rs.getString(i);
if(value == null){
element.setAttribute("xsi:nil","true");
}else{
element.appendChild(myDocument.createTextNode(value));
}
row.appendChild(element);
}
}
return myDocument;
}
}

2、將xml寫成文件

public class FileMaker{
public void static writeFile(String filePath,Sring fileName,String content){
File fileDirectory = new File(filePath);
File targetFile = new File(filePath + File.separator + fileName);
if(!(fileDirectory.isDirectory())){
fileDirectory.mkdirs();//如果傳過來的文件路徑不存在,就先創建這個路徑
}
if(!(targetFile.isFile())){
try{
targetFile.createNewFile();//如果目標文件不存在就創建文件
}catch(IOException e){
//todo
}
}
FileOutputStream fos = null;
try{
fos = new FileOutputStream(targetFile);
org.apache.commons.io.IOUtils.write(content,fos,"UTF-8");
}catch(IOException e){
//todo
}finally{
IOUtils.closeQuietly(fos);
}
}
}

 三、遇到問題

在數據量小時,這種做法能正常工作,但有一天別人在使用的時候系統卡死了,Debug后發現在結果集過大(當時有三百萬條數據)時,內存溢出了。因為依照上面的做法,需要將一個有三百萬條數據的結果集轉成一個Dom對象放在內存中。於是我加大內存,終

於挨過了生成xml這個環節,得到了一個龐大的字符串content。但由於Dom對象的引用雖然被指向了null但它之前所占用的內存並不可能立即釋放,所以在寫文件時內存又不夠了,又溢出。其實,加內存並不是解決問題的辦法,因為數據量不固定,這終究是一

個不健壯的程序。

四、修改方案,解決問題

將結果集硬生生的打造成一個大Dom對象的方式已被證明不可行,我考慮在遍歷結果集的同時邊讀邊寫文件。

感謝 http://www.clipclip.org/wqmd/clips/detail/1204498 的作者。

使用SAX的方式在遍歷結果集的同時生成xml文件。

public void resultSet2XML(ResultSet rs, String rootElementName,String filePath) throws Exception{
SAXTransformerFactory fac = (SAXTransformerFactory)SAXTransfomerFactory.newInstance();
TransformerHandler handler = fac.newTransformerHandler();
Transformer transformer = handler.getTransformer();
Transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION,"no");
Transformer.setOutputProperty(OutputKeys.METHOD,"xml");
Transformer.setOutputProperty(OutputKeys.ENCODING,"UTF-8");
Transformer.setOutputProperty(OutputKeys.INDENT,"yes");
Transformer.setOutputProperty(OutputKeys.STANDALONE,"no");
FileOutputStream fos = new FileOutputStream(filePath);
Result resultxml = new StreamResult(fos);
handler.setResult(resultxml);
ResultSetMetaData rsmd = rs.getMetaData();
String value = "";
AttributeImpl rootElementAttribute = new AttributesImpl();
rootElementAttribute.addAttribute("","","xmlns:xsi","","http://www.w3.org/2001/XMLSchema-instance");
handler.startDocument();
handler.startElement("","",rootElementName,rootElementAttribute);
if(rs.next()){
rs.previous();
while(rs.isLast() == false){
rs.next();
handler.startElement("","",ROW_ELEMENT_NAME,null);
for(int i=1;i<=rsmd.getColumnCount();i++){
int columnType = rsmd.getColumnType();
value = rs.getString(i);
String columnName = rsmd.getColumnLabel(i).toLowerCase();
if(value == null){
AttributesImpl tempAttribute = new AttributesImpl();
tempAttribute.addAttribute("","","xsi:nil","","true");
handler.startElement("","",columnName,tempAttribute);
}else{
handler.startElement("","",columnName,null);
}
handler.character(value.toCharArray(),0,value.length());
handler.endElement("","",ROW_ELEMENT_NAME);
}
handler.endElement("","",rootElementName);
handler.endDocument();
fos.close();
}
}
}

上面的方法執行完的同時,文件輸出流fos也完整地結束了寫文件的工作並關閉,從此,再大的結果集我們都不怕了。

 BTW:XML的標簽不能以數字開頭,本例略去了對節點名是否合法的判斷。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM