【发布时间】:2019-06-13 06:34:06
【问题描述】:
我正在尝试构建一个 Java 应用程序,该应用程序可以将任意 SQL SELECT 查询的非常大的结果集流式传输到 JSONL 文件中,特别是通过 SQLServer,但希望使用任何 JDBC DataSource 运行。在 Python 中,这很容易将 sql 客户端结果视为生成器,然后调用 json.dumps()。然而,在这段代码中,它似乎在写出之前将所有内容都放入内存,通常会导致堆和垃圾收集异常。我需要运行它的查询非常大,最多可以带回 10GB 的原始数据。执行时间不是主要问题,只要每次都能正常工作。
我曾尝试在每一行之后调用 flush(这很荒谬),这似乎对小型数据集有所帮助,但对大型数据集没有帮助。任何人都可以提出一个我可以用来轻松实现这一目标的策略吗?
在我的 SQL 客户端类中,我使用 Apache DbUtils QueryRunner 和 MapListHandler 创建 Maps 列表,这是我需要的灵活性(相对于 Java 中需要指定架构和类型的更传统方法):
public List<Map<String, Object>> query(String queryText) {
try {
DbUtils.loadDriver("com.microsoft.sqlserver.jdbc.Driver");
// this function just sets up all the connection properties. Ommitted for clarity
DataSource ds = this.initDataSource();
StatementConfiguration sc = new StatementConfiguration.Builder().fetchSize(10000).build();
QueryRunner queryRunner = new QueryRunner(ds, sc);
MapListHandler handler = new MapListHandler();
return queryRunner.query(queryText, handler);
} catch (Exception e) {
logger.error(e.getMessage());
e.printStackTrace();
return null;
}
}
JsonLOutputWriter类:
JsonLOutputWriter(String filename) {
GsonBuilder gsonBuilder = new GsonBuilder();
gsonBuilder.serializeNulls();
this.gson = gsonBuilder.create();
try {
this.writer = new PrintWriter(new File(filename), ENCODING);
} catch (FileNotFoundException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
void writeRow(Map row) {
this.writer.println(this.gson.toJson(row));
}
void flush() {
this.writer.flush();
}
主要方法:
JsonLOutputWriter writer = new JsonLOutputWriter(outputFile)
for (Map row : client.query(inputSql)) {
writer.writeRow(row);
}
writer.flush()
【问题讨论】:
-
您使用
QueryRunner的方式意味着前所有行在您写出之前加载到内存中。不要那样做,立即逐行处理。您可能需要深入到 JDBC 级别来执行此操作(如果内存仍然是一个问题,您可能需要使用小于 10000 的提取大小)。还可以考虑使用 bean 而不是Map<String, Object>。 -
@MarkRotteveel 好的,所以我认为我做错了。如果您有正确的示例方法,那就太棒了。
标签: java sql-server jdbc mssql-jdbc apache-commons-dbutils