Необходимо передать большой результат QueryRunner в файл, кажется, хранится в памяти - PullRequest
0 голосов
/ 19 января 2019

Я пытаюсь создать приложение Java, которое может передавать очень большие результирующие наборы произвольных запросов SQL SELECT в файлы JSONL, в частности через SQLServer, но хотел бы работать с любым JDBC DataSource. В Python это было бы легко просто обработать результат клиента SQL как генератор и затем вызвать json.dumps(). Однако в этом коде кажется, что он помещает все в память перед записью, что обычно вызывает исключения из кучи и сборки мусора. Запросы, для которых нужно выполнить эти запросы, очень велики и возвращают до 10 ГБ необработанных данных. Время выполнения не является первостепенной задачей, если оно работает каждый раз.

Я пытался вызывать flush после любого ряда (что смешно), и это, кажется, помогает с маленькими наборами данных, но не с большими. Кто-нибудь может предложить стратегию, которую я могу использовать, чтобы легко это осуществить?

В моем клиентском классе SQL я использую Apache DbUtils QueryRunner и MapListHandler для создания списка Map s, что является необходимой мне гибкостью (по сравнению с более традиционными подходами в 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()

1 Ответ

0 голосов
/ 19 января 2019

В принципе это невозможно сделать с DbUtils из коробки. Я избавился от QueryRunner и MapListHandler, так как обработчик создает ArrayList. Вместо того, чтобы основываться на извлечении, я сделал это на основе толчка, создав очень похожий MyQueryRunner, который принимает MyRowHandler, и вместо возврата коллекции просто перебирает ResultSet и вызывает мою функцию вывода.

Я уверен, что есть более элегантные способы сделать это и вернуть некоторый буфер строк, но это то, что мне нужно 80/20 и работает для больших наборов данных.

RowHandler

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);
    }

}

QueryHandler

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;
    }
}
...