一·、需求
写一个功能类,能将一个给定的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 的作者。
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("",null); } handler.character(value.tocharArray(),value.length()); handler.endElement("",ROW_ELEMENT_NAME); } handler.endElement("",rootElementName); handler.endDocument(); fos.close(); } } }
上面的方法执行完的同时,文件输出流fos也完整地结束了写文件的工作并关闭,从此,再大的结果集我们都不怕了。
BTW:XML的标签不能以数字开头,本例略去了对节点名是否合法的判断。
///////////////////////
众所周知XML已经成不同应用程序之间数据交换的事实上的标准。在实际工作中,我们经常需要把JDBC返回的结果集(ResultSet)转化为XML表达形式,便于把数据传送到其他的应用程序。这里提供一个简单的例子,它可以把ResultSet转化为XML格式的文本,并存放在字符串(String)作为返回结果。这个程序通用之处在于它与选用的数据库结构无关。就是说,如果数据库结构发生了变化,本文提供的程序也可以正确运行。如果你有相同的需要,希望本文能给您一点帮助和启发。
- import java.io.FileOutputStream;
- import java.sql.Connection;
- import java.sql.DriverManager;
- import java.sql.ResultSet;
- import java.sql.ResultSetMetaData;
- import java.sql.sqlException;
- import java.sql.Statement;
- public class ResultSetToXML {
- /**
- * @param ResultSet
- * rs输入的结果集
- * @return String 返回XML串
- * @exception sqlException
- */
- public String generateXML(final ResultSet rs) throws sqlException {
- final StringBuffer buffer = new StringBuffer(1024 * 4);
- if (rs == null)
- return "";
- buffer.append("<?xml version=\"1.0\" encoding=\"GB2312\"?>\n"); // XML的头部信息
- buffer.append("<ResultSet>\n");
- ResultSetMetaData rsmd = rs.getMetaData(); // 得到结果集的定义结构
- int colCount = rsmd.getColumnCount(); // 得到列的总数
- // 对放回的全部数据逐一处理
- for (int id = 1; rs.next(); id++) {
- // 格式为row id,col name,col context
- buffer.append("\t<row id=\"").append(id).append("\">\n");
- for (int i = 1; i <= colCount; i++) {
- String type = rsmd.getColumnTypeName(i); // 获取字段类型
- buffer.append("\t\t<col name=\"" + rsmd.getColumnName(i)
- + "\">");
- buffer.append(getValue(rs,i,type));
- buffer.append("</col>\n");
- }
- buffer.append("\t</row>\n");
- }
- buffer.append("</RowSet>");
- rs.close();
- return buffer.toString();
- }
- /**
- * This method gets the value of the specified column
- * 通用的读取结果集某一列的值并转化为String表达
- *
- * @param ResultSet
- * rs 输入的纪录集
- * @param int
- * colNum 第几列
- * @param int
- * type 数据类型
- */
- private String getValue(final ResultSet rs,int colNum,String type)
- throws sqlException {
- Object value = null;
- if (type.equals("nchar") || type.equals("nvarchar"))
- value = rs.getString(colNum);
- else
- value = rs.getObject(colNum);
- if (value != null)
- return value.toString().trim();
- return "null";
- }
- public static void main(String args[]) {
- ResultSet result;
- try {
- Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
- Connection con = DriverManager.getConnection("jdbc:odbc:MyDataSource","sa","123456");
- // 普通查询
- Statement stat = con.createStatement();
- result = stat.executeQuery("Select * from Orders");
- JDBCToXML obj = new JDBCToXML();
- String res = obj.generateXML(result);
- FileOutputStream fswriter = new FileOutputStream("result.xml",true);
- fswriter.write(res.getBytes());
- fswriter.close();
- con.close();
- } catch (Exception e) {
- System.out.println("Error" + e);
- }
- }
- }
//////////////////
数据查询结果集ResultSet转成xml格式的文件和xml String的方法
1.将ResultSet转成xmL文件:
public static Document toDocument(ResultSet rs)
throws ParserConfigurationException,sqlException
{
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
Document doc = builder.newDocument();
Element results = doc.createElement("Results");
doc.appendChild(results);
ResultSetMetaData rsmd = rs.getMetaData();
int colCount = rsmd.getColumnCount();
while (rs.next())
{
Element row = doc.createElement("Row");
results.appendChild(row);
for (int i = 1; i <= colCount; i++)
{
String columnName = rsmd.getColumnName(i);
Object value = rs.getObject(i);
Element node = doc.createElement(columnName);
node.appendChild(doc.createTextNode(value==null ? "" :value.toString()));
row.appendChild(node);
}
}
return doc;
}
/**
* 序列化DOM为一个字符串
*
*/
public static String ConvertXMLToString(Document doc){
OutputFormat of = new OutputFormat(doc);
of.setIndenting(true);
StringWriter sw = new StringWriter();
XMLSerializer serializer = new XMLSerializer(sw,of);
try {
serializer.serialize(doc);
} catch (IOException e) {
e.printStackTrace();
}
return sw.toString();
}
//main interface
public String GetXMLString(String sqlsentance) {
dbconn();
ResultSet r1=dataset(sqlsentance);
try {
Document dcmt = toDocument(r1);
str= ConvertXMLToString(dcmt);
// xmlstr = sayHello(str);
} catch (ParserConfigurationException e1) {
e1.printStackTrace();
} catch (sqlException e1) {
e1.printStackTrace();
}
dbclose();
return str;
}
2.将查询结果集ResultSet 转换成String类型的xml方法:
public static String convertResultSetToXML(ResultSet rs){
StringBuffer sb = new StringBuffer();
try{
ResultSetMetaData rsmd = rs.getMetaData();
if (rsmd.getColumnCount()>=0){
///sb.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
///sb.append("<response>");
while(rs.next()){
sb.append("<row>\n");
for(int j=1;j<=rsmd.getColumnCount();j++){
sb.append("<" +rsmd.getColumnName(j).toLowerCase()+ ">")
.append( rs.getObject(j)==null?"":rs.getObject(j) )
.append("</" +rsmd.getColumnName(j).toLowerCase()+ ">\n");
}
sb.append("</row>\n");
}
///sb.append("</response>");
}
}catch(Exception e){
e.printStackTrace();
}
return sb.toString();
}
public String Getxmlstring(String sqlsentance){ dbconn(); ResultSet rs1=dataset(sqlsentance); String xmlstr = convertResultSetToXML(rs1); return xmlstr; }