使用Java流使用数据库游标
我想使用Java流使用数据库游标。我希望Java流能够根据需要获取和处理行,并避免先在内存中加载所有500万行,然后再进行处理 是否可以在不将整个表加载到RAM中的情况下使用它 到目前为止,我的代码如下所示:使用Java流使用数据库游标,java,java-stream,ram,database-cursor,Java,Java Stream,Ram,Database Cursor,我想使用Java流使用数据库游标。我希望Java流能够根据需要获取和处理行,并避免先在内存中加载所有500万行,然后再进行处理 是否可以在不将整个表加载到RAM中的情况下使用它 到目前为止,我的代码如下所示: Cursor<Product> products = DAO.selectCursor(...); // 1. Initialize variables long count = 0; ... for (Iterator<Product> it = product
Cursor<Product> products = DAO.selectCursor(...);
// 1. Initialize variables
long count = 0;
...
for (Iterator<Product> it = products.iterator(); it.hasNext();) {
Product p = it.next();
// 2. Processing each row
...
}
// 3. Concluding (processing totals, stats, etc.)
double avg = total / count;
...
cursorproducts=DAO.selectCursor(…);
// 1. 初始化变量
长计数=0;
...
for(Iterator it=products.Iterator();it.hasNext();){
产品p=it.next();
//2.处理每一行
...
}
// 3. 总结(处理总数、统计数据等)
平均双倍=总数/计数;
...
它确实工作得很好,但有点麻烦,我想利用Stream API。首先,我们必须讨论如何从数据库中获取数据。如果您的目的是浏览大量记录,而不希望在内存中同时加载所有记录,则有两种选择:
如果您已经有了一个基于
游标的迭代器
,可以根据需要检索分页数据,那么您可以使用JDK API中的拆分器
和流支持
实用程序类将其转换为流
Stream products=StreamSupport.Stream(
Spliterators.spliteratorUnknownSize(cursor.iterator(),
Spliterator.NONNULL|
分路器|
拆分器(不可变),false)
否则,你将不得不建立自己的东西
驱动程序分页
如果JDBC驱动程序支持fetch size属性,则可以执行以下操作:
Connection con = ds.getConnection();
con.setAutoCommit(false);
PreparedStatement stm = con.prepareStatement("SELECT order_number FROM orders WHERE order_date >= '2018-08-12'", ResultSet.TYPE_FORWARD_ONLY);
stm.setFetchSize(1000);
ResultSet rs = stm.executeQuery();
此时,rs
包含1000条记录的第一次获取,在您阅读上一页之前,它不会从数据库中获取更多记录
所有这一切的棘手之处在于,在读取完所有记录之前,您无法关闭任何资源(即连接、预处理语句和结果集),并且由于我们要构建的流在默认情况下是惰性的,这意味着我们必须保持所有这些资源处于打开状态,直到处理完该流
也许最简单的方法是围绕此逻辑构建一个迭代器,当迭代器实际到达所有数据的末尾时,您可以关闭所有资源(即!rs.next()
),或者另一种方法是在流关闭时完成所有工作(stream.onClose()
)
一旦有了迭代器,使用JDK API中的Spliterators
和StreamSupport
实用程序类从迭代器中构建流就非常简单了
我的基本实现看起来有点像这样。这只是为了举例说明。你可能想对你的特殊情况给予更多的关爱
公共流getUsers(){
DataSource ds=jdbcTemplate.getDataSource();
试一试{
连接conn=ds.getConnection();
连接设置自动提交(错误);
PreparedStatement stm=conn.prepareStatement(“从用户中选择id”,结果集。仅键入“前进”);
//fetch size可以保证一次只有1000条记录
stm.setFetchSize(1000);
ResultSet rs=stm.executeQuery();
迭代器sqlIter=新迭代器(){
@凌驾
公共布尔hasNext(){
试一试{
返回rs.next();
}捕获(SQLE异常){
封闭资源(conn、stm、rs);
抛出新的RuntimeException(“未能从ResultSet中读取记录”,e);
}
}
@凌驾
公共字符串next(){
试一试{
返回rs.getString(“id”);
}捕获(SQLE异常){
封闭资源(conn、stm、rs);
抛出新的RuntimeException(“未能从ResultSet中读取记录”,e);
}
}
};
//将迭代器转换为流
returnstreamsupport.stream(
Spliterators.spliteratorUnknownSize(sqlIter,
Spliterator.NONNULL|
分路器|
拆分器(不可变),false
).onClose(()->{
//确保在处理流时关闭资源
封闭资源(conn、stm、rs);
});
}捕获(SQLE异常){
记录器错误(“处理数据失败”,e);
抛出新的运行时异常(e);
}
}
私有void closeResources(连接连接、准备的语句ps、结果集rs){
try(conn;ps;rs){
logger.info(“资源已成功关闭”);
}捕获(SQLE异常){
logger.warn(“未能正确关闭数据库源”,e);
}
}
这里的关键点是要注意,我们返回的流应该运行一些onClose
逻辑,因此,当我们使用流时,必须确保在使用它时执行stream.close()
,以确保我们关闭到这一点为止保持活动的所有资源(即conn
、stm
和rs
)
最好的方法可能是使用try和资源,这样try将负责关闭流
try(Stream users=userRepo.getUsers()){
//将用户打印到主输出,同时检索1K
users.forEach(System.out::println);
}
手动分页
另一种方法是您自己对结果进行分页,这取决于数据库,但使用诸如limit和offset之类的select子句,您可以请求特定的记录页,处理它们,然后检索更多的记录
从用户限制中选择id 1000偏移量5
在这种情况下,迭代器将消耗所有页面,完成后,请求下一个页面,直到不再重新生成