将一个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的标签不能以数字开头,本例略去了对节点名是否合法的判断。

原文地址:https://www.cnblogs.com/mabaishui/p/2850565.html