Потоковое (курсорное) чтение из базы данных с использованием JdbcTemplate

Spring

Предисловие

В производственной среде подкачка базы данных часто используется для управления объемом данных, получаемых за один раз, и часто при обработке данных используется другой сценарий:

Прочитайте все данные из одной таблицы базы данных для обработки и сохраните результат в другой базе данных или файле или базе данных NoSql.

В настоящее время для пакетной обработки можно также использовать метод подкачки, но этот метод не только логически сложен, но и очень неэффективен.Есть ли способ чтения базы данных, аналогичный файловому потоку? Ответ — да.Стандарт, предложенный JDBC, не только включает потоковое чтение, но также поддерживает считывание и обновление данных в процессе чтения, но в этой статье обсуждается только потоковое (курсорное) чтение базы данных. Давайте посмотрим на реализацию курсора нативного JDBC:

Собственная реализация JDBC

@Test
public void testCursor() throws ClassNotFoundException, SQLException {
    Class.forName("com.mysql.cj.jdbc.Driver");
    Connection connection = DriverManager
            .getConnection("jdbc:mysql://127.0.0.1:3306/db?useUnicode=true&characterEncoding=utf8&useSSL=false", 
                    "root", "root");
    PreparedStatement preparedStatement =
            connection.prepareStatement("select * from table",
                    ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
    preparedStatement.setFetchSize(Integer.MIN_VALUE);

    ResultSet resultSet = preparedStatement.executeQuery();

    while (resultSet.next()) {
        System.err.println(resultSet.getString("id"));
    }
    connection.close();
}

Примечание в MySql

conn.prepareStatement("select * from table",
                      ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); 
// 这里因为MySQL驱动实现使用Integer.MIN_VALUE来判断是否使用流的方式
preparedStatement.setFetchSize(Integer.MIN_VALUE);

Эти две линии незаменимы.

В PostgreSql требуется

connection.setAutoCommit(false);
// 流每一次读取的数据量
preparedStatement.setFetchSize(1000);
tips:
Видно, что разные поставщики баз данных по-прежнему имеют разные реализации.Эта статья только тестируетPostgreSqlЧто касается MySQL, то читатели могут самостоятельно протестировать другие базы данных.

После выполнения видно, что это уже потоковое чтение, поэтому перейдем к делу.Большинство проектов используют JdbcTemplate.Поддерживает ли JdbcTemplate потоковое чтение?

Использование шаблона Jdbc

Откройте JdbcTemplate, и вы увидите сигнатуру метода запроса, как показано ниже:

/**
 * Query using a prepared statement, reading the ResultSet on a per-row basis
 * with a RowCallbackHandler.
 * <p>A PreparedStatementCreator can either be implemented directly or
 * configured through a PreparedStatementCreatorFactory.
 * @param psc a callback that creates a PreparedStatement given a Connection
 * @param rch a callback that will extract results, one row at a time
 * @throws DataAccessException if there is any problem
 * @see PreparedStatementCreatorFactory
 */
void query(PreparedStatementCreator psc, RowCallbackHandler rch) throws DataAccessException;

Ключевой момент заключается в параметрах PreparedStatementCreator и RowCallbackHandler. Мы можем управлять созданием PreparedStatement и извлечением ResultSet, что очень просто. Конкретный код реализован следующим образом:

MySQL

jdbcTemplate.query(con -> {
            PreparedStatement preparedStatement =
                    con.prepareStatement("select * from table",
                                         ResultSet.TYPE_FORWARD_ONLY, 
                                                   ResultSet.CONCUR_READ_ONLY);
            preparedStatement.setFetchSize(Integer.MIN_VALUE);
            preparedStatement.setFetchDirection(ResultSet.FETCH_FORWARD);
            return preparedStatement;
        }, rs -> {
            while (rs.next()) {
                System.err.println(resultSet.getString("id"));
            }
        });

PostgreSql

jdbcTemplate.query(con -> {
            con.setAutoCommit(false);
            PreparedStatement preparedStatement =
                    con.prepareStatement("select * from table",
                                         ResultSet.TYPE_FORWARD_ONLY, 
                                                   ResultSet.CONCUR_READ_ONLY);
            preparedStatement.setFetchSize(1000);
            preparedStatement.setFetchDirection(ResultSet.FETCH_FORWARD);
            return preparedStatement;
        }, rs -> {
            while (rs.next()) {
                System.err.println(resultSet.getString("id"));
            }
        });

Таким образом, нам больше не нужно писать уродливый код шаблона JDBC.

Ссылаться на:docs.Oracle.com/Java Color/Picture О...