在JDBC开发中,ResultSet是查询结果的标准载体,但很多下游系统需要的是XML格式的数据。无论是给老系统做接口对接,还是生成配置文件、导出报表,把ResultSet转换成XML都是一个很实际的诉求。本文介绍三种常见实现方式,从最直接的手动拼接到自动化的库方案,并讨论各自的适用场景。

方式一:手动拼接XML字符串
最直观的思路是遍历ResultSet,按照XML的语法规则把每行数据拼成一个字符串。这种方式不依赖任何第三方库,只需要StringBuilder就能完成,适合字段结构简单、对性能不敏感的场景。
具体做法是:先通过ResultSetMetaData获取列的数量和列名,列名可以直接作为XML标签名,再循环读取每一行的值。示例代码如下:
public String resultSetToXml(ResultSet rs) throws SQLException {
StringBuilder xml = new StringBuilder();
xml.append("<results>");
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
while (rs.next()) {
xml.append("<row>");
for (int i = 1; i <= columnCount; i++) {
String columnName = meta.getColumnName(i);
String value = rs.getString(i);
xml.append("<").append(columnName).append(">")
.append(escapeXml(value))
.append("</").append(columnName).append(">");
}
xml.append("</row>");
}
xml.append("</results>");
return xml.toString();
}
// 转义XML特殊字符,避免解析失败
private String escapeXml(String input) {
if (input == null) return "";
return input.replace("&", "&")
.replace("<", "<")
.replace(">", ">")
.replace("\"", """)
.replace("'", "'");
}
这里有几个细节值得注意。第一,escapeXml方法不可省略,如果数据库里存的内容包含<、&等字符,直接拼接会导致生成的XML无法解析。第二,拼接时要注意列名必须是合法的XML标签名,如果列名以数字开头或者包含空格(比如Oracle返回的列名带引号),需要先做清洗,比如用正则替换成下划线。
这种方式的优点是完全可控、零依赖,缺点是容易出错,而且拼接出来的只是字符串,如果想再加工(比如改名空间、调整层级)就得重新处理。另外,StringBuilder在结果集很大时占用内存较多,需要评估数据量。
方式二:使用DOM构建Document对象
如果希望得到一个结构化的XML文档对象,而不是一串字符串,可以用JDK自带的DOM API来构建Document。这种方式的好处是转换结果是标准的DOM树,后续可以继续修改节点、添加属性,最后再序列化输出。
核心步骤是:用DocumentBuilderFactory创建一个空文档,为每行数据创建一个row元素,每个字段作为子元素挂上去。代码示例如下:
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.w3c.dom.*;
import java.io.StringWriter;
import java.sql.*;
public Document buildXmlDocument(ResultSet rs) throws Exception {
Document doc = DocumentBuilderFactory.newInstance()
.newDocumentBuilder().newDocument();
Element root = doc.createElement("results");
doc.appendChild(root);
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
while (rs.next()) {
Element row = doc.createElement("row");
for (int i = 1; i <= columnCount; i++) {
String columnName = meta.getColumnLabel(i);
Element field = doc.createElement(columnName);
String value = rs.getString(i);
field.setTextContent(value == null ? "" : value);
row.appendChild(field);
}
root.appendChild(row);
}
return doc;
}
public String documentToString(Document doc) throws Exception {
Transformer transformer = TransformerFactory.newInstance().newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
StringWriter writer = new StringWriter();
transformer.transform(new DOMSource(doc), new StreamResult(writer));
return writer.getBuffer().toString();
}
使用DOM方式时,特殊字符的转义由API自动完成,不需要自己写escapeXml,这比手动拼接安全得多。此外,getColumnLabel比getColumnName更推荐使用,因为它返回的是SELECT语句中的别名,当查询中使用了AS重命名时,别名才是我们期望的标签名。
这种方式的短板在于内存开销。DOM会把整棵树加载到内存,对于几万行以上的结果集,内存压力会明显上升,甚至触发频繁的Full GC。如果数据量大,可以考虑后面提到的流式方案。
方式三:借助第三方库自动转换
除了手写代码,一些库提供了ResultSet到XML的直接映射能力。比如Apache的DbUtils虽然主打对象映射,但可以配合工具类快速实现;而老的javax.sql.rowset.WebRowSet更是JDK内置的方案,它本身就支持以XML形式读写结果集。
WebRowSet的用法非常简单,把ResultSet填充进去之后,调用writeXml方法即可输出:
import javax.sql.rowset.WebRowSet;
import javax.sql.rowset.RowSetProvider;
import java.sql.*;
public void writeWithWebRowSet(ResultSet rs, String filePath) throws Exception {
WebRowSet rowSet = RowSetProvider.newFactory().createWebRowSet();
rowSet.populate(rs); // 填充ResultSet数据
try (java.io.FileWriter fw = new java.io.FileWriter(filePath)) {
rowSet.writeXml(fw); // 直接输出XML文件
}
rowSet.close();
}
WebRowSet生成的XML带有自己的命名空间和元数据描述(包含列类型、是否可为空等信息),格式是标准化的,适合需要与其他Java系统交换数据的场合。但缺点也很明显:输出格式固定,标签结构由规范决定,如果想自定义标签名或者只输出部分字段,灵活性就不如前两种方式。
如果项目本身已经在用MyBatis或Hibernate,也可以考虑在ORM层直接把查询结果序列化为XML,这样连ResultSet的处理都省掉了,不过这已经超出本文范围,按需选择即可。
大结果集的流式处理与选型建议
当查询结果达到几十万行时,无论拼接字符串还是构建DOM,都会面临内存问题。此时应该换成流式思路:一边读ResultSet一边写XML,写完一段就释放一段。可以用TransformerHandler(SAX方式的序列化器)配合StreamResult直接输出到文件或响应流:
import javax.xml.transform.sax.SAXTransformerFactory;
import javax.xml.transform.sax.TransformerHandler;
import javax.xml.transform.stream.StreamResult;
import org.xml.sax.helpers.AttributesImpl;
import java.io.*;
public void streamXml(ResultSet rs, OutputStream out) throws Exception {
SAXTransformerFactory factory = (SAXTransformerFactory)
SAXTransformerFactory.newInstance();
TransformerHandler handler = factory.newTransformerHandler();
handler.setResult(new StreamResult(out));
handler.startDocument();
handler.startElement("", "", "results", new AttributesImpl());
ResultSetMetaData meta = rs.getMetaData();
int columnCount = meta.getColumnCount();
while (rs.next()) {
handler.startElement("", "", "row", new AttributesImpl());
for (int i = 1; i <= columnCount; i++) {
String name = meta.getColumnLabel(i);
handler.startElement("", "", name, new AttributesImpl());
String value = rs.getString(i);
if (value != null) {
handler.characters(value.toCharArray(), 0, value.length());
}
handler.endElement("", "", name);
}
handler.endElement("", "", "row");
}
handler.endElement("", "", "results");
handler.endDocument();
}
注意使用流式方案时,JDBC这边最好也做配套设置。例如MySQL驱动可以在连接串中加上useCursorFetch=true,并把Statement的fetchSize设置为一个合理的值(比如500),这样数据库不会一次性把全部结果推给客户端,内存才能真正做到平稳。
关于选型,可以简单总结:数据量小、格式简单,用手动拼接或WebRowSet快速搞定;需要在转换后再加工文档,选DOM方式;数据量大或者要直接写文件、写HTTP响应,优先考虑SAX流式处理。无论选哪种,都不要忘记处理NULL值和特殊字符转义,这两点是实际项目中XML解析报错的高发原因。
ResultSet转XMLJava XMLDOM解析修改时间:2026-09-09 22:44:45