Pagination pattern 是为了网站展示而发明的(与滚动导航相反),在那里效果最好。简而言之,实时用户实际上无法一次查看数千/百万条记录,因此将信息分为短页(50~200 条记录),通常每页向数据库发送一次查询。用户通常只点击几个页面,并没有浏览所有页面,另外用户浏览页面需要一点时间,所以查询不是一一发送到数据库,而是间隔很长。检索一块数据的时间比检索所有数百万条记录的时间要短得多,因此用户很高兴,因为他不必等待很长时间才能等待后续页面,并且整体系统负载更小。
但从问题来看,您的应用程序的性质似乎是面向批处理而不是网络演示。应用程序必须获取所有记录并对每条记录执行一些操作/转换(计算)。在这种情况下,使用了完全不同的设计模式(流/流水线处理、步骤顺序、并行步骤/操作等),并且分页将不起作用,如果您这样做会降低系统性能。强>
让我们看一个简单实用的例子,而不是花哨的理论,它会告诉你我们在这里讨论的速度差异
假设有一个表PAGINATION,大约有700万条记录:
create table pagination as
select sysdate - 200 * dbms_random.value As my_date, t.*
from (
select o.* from all_objects o
cross join (select * from dual connect by level <= 100)
fetch first 10000000 rows only
) t;
select count(*) from pagination;
COUNT(*)
----------
7369600
假设在MY_DATE 列上创建了一个索引,并且索引统计信息是新鲜的:
create index PAGINATION_IX on pagination( my_date );
BEGIN dbms_stats.gather_table_stats( 'TEST', 'PAGINATION', method_opt => 'FOR ALL COLUMNS' ); END;
/
假设我们将在以下日期之间处理表中大约 10% 的记录:
select count(*) from pagination
where my_date between date '2017-10-01' and '2017-10-21';
COUNT(*)
----------
736341
最后,为了简单起见,我们的“处理”将包括对字段之一的长度进行简单求和。
这是一个简单的分页实现:
public class Pagination {
public static class RecordPojo {
Date myDate;
String objectName;
public Date getMyDate() {
return myDate;
}
public RecordPojo setMyDate(Date myDate) {
this.myDate = myDate;
return this;
}
public String getObjectName() {
return objectName;
}
public RecordPojo setObjectName(String objectName) {
this.objectName = objectName;
return this;
}
};
static class MyPaginator{
private Connection conn;
private int pageSize;
private int currentPage = 0;
public MyPaginator( Connection conn, int pageSize ) {
this.conn = conn;
this.pageSize = pageSize;
}
static final String QUERY = ""
+ "SELECT my_date, object_name FROM pagination "
+ "WHERE my_date between date '2017-10-01' and '2017-10-21' "
+ "ORDER BY my_date "
+ "OFFSET ? ROWS FETCH NEXT ? ROWS ONLY";
List<RecordPojo> getNextPage() {
List<RecordPojo> list = new ArrayList<>();
ResultSet rs = null;
try( PreparedStatement ps = conn.prepareStatement(QUERY);) {
ps.setInt(1, pageSize * currentPage++ );
ps.setInt(2, pageSize);
rs = ps.executeQuery();
while( rs.next()) {
list.add( new RecordPojo().setMyDate(rs.getDate(1)).setObjectName(rs.getString(2)));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
try{rs.close();}catch(Exception e) {}
}
return list;
}
public int getCurrentPage() {
return currentPage;
}
}
public static void main(String ...x) throws SQLException {
OracleDataSource ds = new OracleDataSource();
ds.setURL("jdbc:oracle:thin:test/test@//localhost:1521/orcl");
long startTime = System.currentTimeMillis();
long value = 0;
int pageSize = 1000;
try( Connection conn = ds.getConnection();){
MyPaginator p = new MyPaginator(conn, pageSize);
List<RecordPojo> list;
while( ( list = p.getNextPage()).size() > 0 ) {
value += list.stream().map( y -> y.getObjectName().length()).mapToLong(Integer::longValue).sum();
System.out.println("Page: " + p.getCurrentPage());
}
System.out.format("==================\nValue = %d, Pages = %d, time = %d seconds", value, p.getCurrentPage(), (System.currentTimeMillis() - startTime)/1000);
}
}
}
结果是:
Value = 18312338, Pages = 738, time = 2216 seconds
现在让我们测试一个非常简单的基于流的解决方案 - 只获取一条记录,处理它,丢弃它(释放内存),然后获取下一条记录。
public class NoPagination {
static final String QUERY = ""
+ "SELECT my_date, object_name FROM pagination "
+ "WHERE my_date between date '2017-10-01' and '2017-10-21' "
+ "ORDER BY my_date ";
public static void main(String[] args) throws SQLException {
OracleDataSource ds = new OracleDataSource();
ds.setURL("jdbc:oracle:thin:test/test@//localhost:1521/orcl");
long startTime = System.currentTimeMillis();
long count = 0;
ResultSet rs = null;
PreparedStatement ps = null;
try( Connection conn = ds.getConnection();){
ps = conn.prepareStatement(QUERY);
rs = ps.executeQuery();
while( rs.next()) {
// processing
RecordPojo r = new RecordPojo().setMyDate(rs.getDate(1)).setObjectName(rs.getString(2));
count+=r.getObjectName().length();
}
System.out.format("==================\nValue = %d, time = %d seconds", count, (System.currentTimeMillis() - startTime)/1000);
}finally {
try { rs.close();}catch(Exception e) {}
try { ps.close();}catch(Exception e) {}
}
}
结果是:
Value = 18312328, time = 11 seconds
是 - 2216 秒 / 11 秒 = 快 201 倍 - 快 20 100 % !!!
逆天 ?您可以自己测试。
这个例子说明了选择正确的解决方案(正确的设计模式)来解决问题的重要性。