前言

生产环境中经常使用数据库分页的方式来控制一次获取的数据量,而数据处理中经常会有另外一种场景:

从一个数据库表中读取所有数据进行处理并将结果保存在其他数据库或文件或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是否支持流式读取呢?

JdbcTemplate使用

打开JdbcTemplate可以看到有一个query方法签名如下:

/**

* Query using a prepared statement, reading the ResultSet on a per-row basis

* with a RowCallbackHandler.

*

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模板代码了。

Logo

一站式 AI 云服务平台

更多推荐