1. 概述
当处理与数据库交互的 Java 应用程序时,我们经常需要将查询结果导出为易于消费或共享的格式。CSV(逗号分隔值)是常用的格式之一。为此,我们可以将 JDBC ResultSet
(用于保存数据库查询结果的数据结构)转换为 CSV 文件。
本文将探讨两种将 ResultSet
转换为 CSV 格式的方法。
2. 环境设置
为了测试,我们将使用具有以下架构的 Employees
表:
CREATE TABLE EMPLOYEES (
id SERIAL PRIMARY KEY ,
first_name VARCHAR(50),
last_name VARCHAR(50),
salary DECIMAL(10, 2)
);
3. 使用自定义逻辑
在这种方法中,我们首先查询 Employees
表获取记录:
如上所示,Employees
表记录包含特殊字符。然后我们遍历 ResultSet
并将每条记录转换为 CSV:
List<String> toCsv(Connection connection) throws SQLException {
List<String> csvRecords = new ArrayList<>();
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM employees");
ResultSet resultSet = preparedStatement.executeQuery();
while (resultSet.next()) {
String eachRecord = formatForCsv(resultSet.getString("first_name")) + "," +
formatForCsv(resultSet.getString("last_name")) + "," +
"\"" + resultSet.getDouble("salary") + "\"";
csvRecords.add(eachRecord);
}
return csvRecords;
}
对于每条记录,我们将每个列(如 first_name
、last_name
等)作为字符串读取。我们创建了 formatForCSV()
辅助方法,用于将换行符(\n
)转义为 \\n
,回车符(\r
)转义为 \\r
,双引号("
)转义为 ""
:
String formatForCsv(String value) {
return "\"" + value
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\"", "\"\"")
+ "\"";
}
如果执行我们的逻辑,我们将获得 ResultSet
转换的 CSV 结果:
我们可以编写单元测试来验证逻辑是否按预期工作:
@Test
void givenEmployeeRecordsInEmployeeTable_whenResultSetToCSVInvoked_thenReturnsCSV() throws SQLException, IOException {
insertRecords();
ResultSetToCSV logic = new ResultSetToCSV();
List<String> csvRecords = logic.toCsv(connection);
Files.write(Paths.get("/Users/surajmishra/Documents/work-space/employee.csv"), csvRecords);
assertThat(csvRecords.size()).isEqualTo(3);
for (int i = 1; i <= 2; i++) {
assertThat(csvRecords.get(i - 1))
.isEqualTo("\"first" + i + "\"," + "\"last" + i + "\"," + "\"" + String.format("%.1f", 100.00 * i) + "\"");
}
assertThat(csvRecords.get(2))
.isEqualTo("\"\"\"first\\nfirst1\\nfirst2!\"\"1\"," + "\"\"\"last!\\nlast1!\"\"1\"," + "\"100.0\"");
}
4. 使用第三方依赖
有多种开源库可以帮助我们将 ResultSet
转换为 CSV。其中流行的库之一是 OpenCSV。
我们可以在 pom.xml
文件中添加它:
<dependency>
<groupId>com.opencsv</groupId>
<artifactId>opencsv</artifactId>
<version>5.9</version>
</dependency>
在下面的逻辑中,我们从数据库读取所有 Employees
记录作为 ResultSet
,然后使用 OpenCSV 将它们转换为 CSV:
String toCsvWithOpenCsv(Connection connection) throws SQLException {
PreparedStatement preparedStatement = connection.prepareStatement("SELECT * FROM employees");
ResultSet resultSet = preparedStatement.executeQuery();
StringWriter stringWriter = new StringWriter();
CSVWriter csvWriter = new CSVWriter(stringWriter,
CSVWriter.DEFAULT_SEPARATOR,
CSVWriter.DEFAULT_QUOTE_CHARACTER, // 默认引号字符是双引号
CSVWriter.DEFAULT_ESCAPE_CHARACTER, // 默认转义字符是双引号
CSVWriter.DEFAULT_LINE_END);
ResultSetMetaData metaData = resultSet.getMetaData();
int columnCount = metaData.getColumnCount();
String[] row = new String[columnCount-1];
while (resultSet.next()) {
row[0] = resultSet.getString("first_name")
.replace("\n", "\\n")
.replace("\r", "\\r");
row[1] = resultSet.getString("last_name")
.replace("\n", "\\n")
.replace("\r", "\\r");
row[2] = String.valueOf(resultSet.getDouble("salary"));
csvWriter.writeNext(row);
}
return stringWriter.toString();
}
在上述逻辑中,我们还对每行进行转义处理,处理列值中的任何特殊字符,如换行符、回车符或双引号。我们使用 csvWriter
为 CSV 写入行数组。然后为了获取 String
,我们使用 toString()
方法将包含 CSV 数据的 stringWriter
转换为 String
。
我们可以编写单元测试来验证逻辑是否按预期工作:
@Test
void givenEmployeeRecordsInEmployeeTable_whenResultSetToCSVWithOpenCsvInvoked_thenReturnsCSV() throws SQLException {
insertRecords();
ResultSetToCSV logic = new ResultSetToCSV();
String csvRecords = logic.toCsvWithOpenCsv(connection);
String[] split = csvRecords.split("\n");
assertThat(split.length).isEqualTo(3);
for (int i = 1; i <= 2; i++) {
assertThat(split[i - 1])
.isEqualTo("\"first" + i + "\"," + "\"last" + i + "\"," + "\"" + String.format("%.1f", 100.00 * i) + "\"");
}
assertThat(split[2])
.isEqualTo("\"\"\"first\\nfirst1\\nfirst2!\"\"1\"," + "\"\"\"last!\\nlast1!\"\"1\"," + "\"100.0\"");
}
5. 总结
在本教程中,我们学习了使用 OpenCSV 和自定义逻辑将 JDBC ResultSet
转换为 CSV 的两种方法。虽然我们可以始终使用第三方依赖(如 OpenCSV)来执行转换,但编写自定义逻辑也并不复杂。