【问题标题】:Need to stream large QueryRunner result to file, seems to be storing in memory需要将大型 QueryRunner 结果流式传输到文件,似乎存储在内存中
【发布时间】:2019-06-13 06:34:06
【问题描述】:

我正在尝试构建一个 Java 应用程序,该应用程序可以将任意 SQL SELECT 查询的非常大的结果集流式传输到 JSONL 文件中,特别是通过 SQLServer,但希望使用任何 JDBC DataSource 运行。在 Python 中,这很容易将 sql 客户端结果视为生成器,然后调用 json.dumps()。然而,在这段代码中,它似乎在写出之前将所有内容都放入内存,通常会导致堆和垃圾收集异常。我需要运行它的查询非常大,最多可以带回 10GB 的原始数据。执行时间不是主要问题,只要每次都能正常工作。

我曾尝试在每一行之后调用 flush(这很荒谬),这似乎对小型数据集有所帮助,但对大型数据集没有帮助。任何人都可以提出一个我可以用来轻松实现这一目标的策略吗?

在我的 SQL 客户端类中,我使用 Apache DbUtils QueryRunnerMapListHandler 创建 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&lt;String, Object&gt;
  • @MarkRotteveel 好的,所以我认为我做错了。如果您有正确的示例方法,那就太棒了。

标签: java sql-server jdbc mssql-jdbc apache-commons-dbutils


【解决方案1】:

基本上,开箱即用的DbUtils 无法做到这一点。我摆脱了QueryRunnerMapListHandler,因为处理程序创建了ArrayList。我不是基于拉,而是基于推,创建了一个非常相似的MyQueryRunner,它采用MyRowHandler,而不是返回一个集合,而是迭代ResultSet并调用我的输出函数。

我确信有更优雅的方法可以做到这一点并返回某种行缓冲区,但这是我需要的 80/20,适用于大型数据集。

行处理程序

public class RowHandler {
    private static final RowProcessor ROW_PROCESSOR = new BasicRowProcessor();
    private JsonLOutputWriter writer;

    public RowHandler(JsonLOutputWriter writer) {
        this.writer = writer;
    }

    int handle(ResultSet rs) throws SQLException {
        AtomicInteger counter = new AtomicInteger();
        while (rs.next()) {
            writer.writeRow(this.handleRow(rs));
            counter.getAndIncrement();
        }
        return counter.intValue();
    }

    protected Map<String, Object> handleRow(ResultSet rs) throws SQLException {
        return this.ROW_PROCESSOR.toMap(rs);
    }

}

查询处理程序

class CustomQueryRunner extends AbstractQueryRunner {

    private final RowHandler rh;

    CustomQueryRunner(DataSource ds, StatementConfiguration stmtConfig, RowHandler rh) {
        super(ds, stmtConfig);
        this.rh = rh;
    }

    int query(String sql) throws SQLException {
        Connection conn = this.prepareConnection();
        return this.query(conn, true, sql);
    }

    private int query(Connection conn, boolean closeConn, String sql, Object... params)
            throws SQLException {
        if (conn == null) {
            throw new SQLException("Null connection");
        }
        PreparedStatement stmt = null;
        ResultSet rs = null;
        int count = 0;
        try {
            stmt = this.prepareStatement(conn, sql);
            this.fillStatement(stmt, params);
            rs = this.wrap(stmt.executeQuery());
            count = rh.handle(rs);
        } catch (SQLException e) {
            this.rethrow(e, sql, params);
        } finally {
            try {
                close(rs);
            } finally {
                close(stmt);
                if (closeConn) {
                    close(conn);
                }
            }
        }
        return count;
    }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-13
    • 2019-10-14
    • 1970-01-01
    • 2018-06-28
    • 1970-01-01
    • 2014-02-09
    相关资源
    最近更新 更多