Java 需要将大型QueryRunner结果流式传输到文件,似乎存储在内存中

Java 需要将大型QueryRunner结果流式传输到文件,似乎存储在内存中,java,sql-server,jdbc,mssql-jdbc,apache-commons-dbutils,Java,Sql Server,Jdbc,Mssql Jdbc,Apache Commons Dbutils,我正在尝试构建一个Java应用程序,该应用程序可以将任意SQL SELECT查询的非常大的结果集流式传输到JSONL文件中,特别是通过SQLServer,但希望使用任何JDBC数据源运行。在Python中,只需将sql客户机结果视为生成器,然后调用json.dumps(),就很容易了。然而,在这段代码中,它似乎是在写之前将所有内容都放在内存中,通常会导致堆和垃圾收集异常。我需要为其运行的查询非常大,可以返回高达10GB的原始数据。执行时间不是主要关注点,只要它每次都有效 我尝试过调用flush

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

我尝试过调用flush after-ever-row(这很荒谬),这似乎对小数据集有帮助,但对大数据集没有帮助。有谁能提出一个策略,我可以用它来轻松实现这一点

在我的SQL客户机类中,我使用ApacheDbutils
QueryRunner
MapListHandler
创建
Map
的列表,这是我所需要的灵活性(与Java中需要指定模式和类型的更传统的方法相比):

主要方法:

JsonLOutputWriter writer = new JsonLOutputWriter(outputFile)
for (Map row : client.query(inputSql)) {
    writer.writeRow(row);
}
writer.flush()

基本上,这不能用现成的
DbUtils
来完成。我去掉了
QueryRunner
MapListHandler
,因为处理程序创建了
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;
    }
}

您使用
QueryRunner
的方式意味着,首先将所有行加载到内存中,然后再将其写出。不要这样做,立即逐行处理。为此,您可能需要深入到JDBC级别(如果内存仍然存在问题,则可能需要使用小于10000的获取大小)。也可以考虑使用bean而不是<代码> map <代码>。如果你有一个正确的例子,那将是惊人的。
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;
    }
}