一.邏輯分頁
1.邏輯分頁的第一種方式,利用ResultSet的滾動分頁。步驟如下:
a.根據條件sql查詢數據庫。
b.得到ResultSet的結果集,由於ResultSet帶有游標,因此可以使用其next()方法來指向下一條記錄。
c.利用next()方法,得到分頁所需的結果集。
這種分頁方式依靠的是對結果集的算法來分頁,因此通常被稱為“邏輯分頁”。
代碼如下:
/**
* TestPageResultSetDAO.java
*
* Copyright 2008. All Rights Reserved.
*/
package com.cosmow.pageresultset.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.cosmow.pageresultset.entity.Bars;

/**
* TODO dao class TestPageResultSetDAO
*
* Revision History
*
* 2008-7-7,Cosmo,created it
*/
public class TestPageResultSetDAO {

private final String FIND_BARS_PAGE = "SELECT * FROM YYBARS ORDER BY id";

/**
* 提供JDBC連接方法,返回一個Connection的實例
*
* @return
* @throws SQLException
*/
private Connection getConnection() throws SQLException {
try {
final String url = "jdbc:oracle:thin:@localhost:1521:ORCL";
final String user = "store";
final String password = "store_password";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url, user, password);
return con;
} catch (ClassNotFoundException e) {
throw new SQLException(e.getMessage());
}
}

/**
* 邏輯分頁方法一,該方法使用移位(rs.next)來進行
*
* @param currentPage
* 當前頁
* @param showRows
* 一頁顯示的數據量
*/
public List<Bars> pageListOne(int currentPage, int showRows) {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
ArrayList<Bars> resultList = new ArrayList<Bars>();
try {
con = getConnection();
ps = con.prepareStatement(FIND_BARS_PAGE);
rs = ps.executeQuery();
// 過濾結果集的變量
int skipBegin = (currentPage - 1) * showRows;
int skipEnd = currentPage * showRows;
// 翻頁計數器
int currentNum = 0;
// 當返回結果集中有記錄時
while (rs.next()) {
// 以下情況將保證在結果集中有記錄時的應用
if (currentNum >= skipBegin && currentNum < skipEnd) {
Bars bar = new Bars();
bar.setId(rs.getLong("id"));
bar.setName(rs.getString("name"));
bar.setType(rs.getInt("type"));
bar.setCreatorId(rs.getLong("creator_id"));
resultList.add(bar);
if (currentNum == skipEnd - 1)
break;
}
currentNum++;
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (ps != null)
ps.close();
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return resultList;
}
}
2.邏輯分頁的第二種方式
利用Scrollable ResultSets(可滾動結果集合)來快速定位到某個游標所指定的記錄行,所使用的是ResultSet的absolute()方法。
改進代碼如下:
/**
* TestPageResultSetDAO.java
*
* Copyright 2008. All Rights Reserved.
*/
package com.cosmow.pageresultset.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.cosmow.pageresultset.entity.Bars;

/**
* TODO dao class TestPageResultSetDAO
*
* Revision History
*
* 2008-7-7,Cosmo,created it
*/
public class TestPageResultSetDAO {

private final String FIND_BARS_PAGE = "SELECT * FROM YYBARS ORDER BY id";
/**
* 提供JDBC連接方法,返回一個Connection的實例
*
* @return
* @throws SQLException
*/
private Connection getConnection() throws SQLException {
try {
final String url = "jdbc:oracle:thin:@localhost:1521:ORCL";
final String user = "store";
final String password = "store_password";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url, user, password);
return con;
} catch (ClassNotFoundException e) {
throw new SQLException(e.getMessage());
}
}

/**
* 邏輯分頁方法二,使用absolute()方法分頁
*
* @param currentPage
* 當前頁
* @param showRows
* 一頁顯示的數據量
*/
public List<Bars> pageListTwo(int currentPage, int showRows) {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
ArrayList<Bars> resultList = new ArrayList<Bars>();
try {
con = getConnection();
ps = con.prepareStatement(FIND_BARS_PAGE);
rs = ps.executeQuery();

// 過濾結果集的變量
int skipBegin = (currentPage - 1) * showRows;
int skipEnd = currentPage * showRows;
// 利用rs.absolute進行定位
if (!rs.absolute(skipBegin))
return resultList;
// 當返回結果集中有記錄時
while (rs.next()) {
// 以下情況將保證在結果集中有記錄時的應用
if (skipBegin < skipEnd) {
Bars bar = new Bars();
bar.setId(rs.getLong("id"));
bar.setName(rs.getString("name"));
bar.setType(rs.getInt("type"));
bar.setCreatorId(rs.getLong("creator_id"));
resultList.add(bar);
if (skipBegin == skipEnd - 1)
break;
}
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (ps != null)
ps.close();
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return resultList;
}
}
雖然和第一種方式區別不大,單效率比ResultSet滾動要好,但是absolute()方法並不是所有jdbc驅動都支持。
可用如下代碼測試當前jdbc驅動是否支持可滾動結果集:

int type = rs.getType();

if (type == ResultSet.TYPE_SCROLL_INSENSITIVE || type == ResultSet.TYPE_SCROLL_SENSITIVE)

System.out.println("Result set is scrollable");

else

System.out.println("Result set is not scrollable");
二.物理分頁
利用數據庫本身的一些特性來分頁。即:利用了數據庫對sql語法的優化,提高分頁性能。
1.針對Oracle數據庫
步驟如下:
a.根據所使用的數據庫特性來組織sql進行分頁。
b.每次跳轉頁面的sql查詢都不相同。
通用的sql分頁方式,“限制行數結果集的倒序”分頁,步驟如下:
(1).取得符合條件的所有結果集中可以唯一標識的Key值(通常是主鍵),並正向排序。
(2).利用數據庫提供的特殊方法進行“最大結果集”的限制(在Oracle中使用rownum, sql server中使用top, mysql中使用limit...),
該“最大結果集”指包含當前所處頁的所有記錄數,“最大結果集”應該只包含惟一的Key值。
(3).對步驟(2)中的“最大結果集”進行逆序,並取得“顯示當前頁顯示數量的結果集”,該結果集中只包含惟一的Key值。
(4).通過步驟(3)中所取得的Key值取得顯示數據,該顯示數據就是當前頁應該顯示的數據。
代碼如下:
/**
* TestPageResultSetDAO.java
*
* Copyright 2008. All Rights Reserved.
*/
package com.cosmow.pageresultset.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.cosmow.pageresultset.entity.Bars;

/**
* TODO dao class TestPageResultSetDAO
*
* Revision History
*
* 2008-7-7,Cosmo,created it
*/
public class TestPageResultSetDAO {

//針對Oracle
private final String FIND_BARS_ORACLE = "select b3.* from "
+ "(select b1.id from "
+ "(select b.id from yybars b where rownum <= ? order by b.id desc) b1 "
+ "where rownum <= ? order by b1.id desc) b2, "
+ "yybars b3 where b2.id = b3.id order by b2.id";
/**
* 提供JDBC連接方法,返回一個Connection的實例
*
* @return
* @throws SQLException
*/
private Connection getConnection() throws SQLException {
try {
final String url = "jdbc:oracle:thin:@localhost:1521:ORCL";
final String user = "store";
final String password = "store_password";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url, user, password);
return con;
} catch (ClassNotFoundException e) {
throw new SQLException(e.getMessage());
}
}

/**
* 物理分頁方法一針對Oracle,使用sql語句的id查詢來進行
*
* @param currentPage
* 當前頁
* @param showRows
* 一頁顯示的數據量
*/
public List<Bars> pageListThree(int currentPage, int showRows) {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
ArrayList<Bars> resultList = new ArrayList<Bars>();
try {
con = getConnection();
ps = con.prepareStatement(FIND_BARS_ORACLE);
//傳入參數,第一個參數標示包含該頁總共有幾條數據
ps.setInt(1, showRows * currentPage);
//第二個參數標示將取得在第一個參數所指定的數據中應該顯示的數據
ps.setInt(2, showRows);
rs = ps.executeQuery();
// 當返回結果集中有記錄時
while (rs.next()) {
Bars bar = new Bars();
bar.setId(rs.getLong("id"));
bar.setName(rs.getString("name"));
bar.setType(rs.getInt("type"));
bar.setCreatorId(rs.getLong("creator_id"));
resultList.add(bar);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (ps != null)
ps.close();
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return resultList;
}
}
2.針對MySQL數據庫
在MySQL數據庫中offset關鍵字的意思是"越過",而limit關鍵字的意思是“限制”,利用這兩者結合可輕松分頁。
(1)取得符合條件的結果集,包含全字段。
(2)利用offset關鍵字越過一段結果集(被越過的結果集就是"(當前頁 - 1) * 一頁顯示數")。
(3)利用limit關鍵字限制取得一段結果集(被限制取得的結果集就是一頁顯示數)
代碼如下:
/**
* TestPageResultSetDAO.java
*
* Copyright 2008. All Rights Reserved.
*/
package com.cosmow.pageresultset.dao;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import com.cosmow.pageresultset.entity.Bars;

/**
* TODO dao class TestPageResultSetDAO
*
* Revision History
*
* 2008-7-7,Cosmo,created it
*/
public class TestPageResultSetDAO {
private final String FIND_BARS_MYSQL = "select * from yybars order by id limit ? offset ?";

/**
* 提供JDBC連接方法,返回一個Connection的實例
*
* @return
* @throws SQLException
*/
private Connection getConnection() throws SQLException {
try {
final String url = "jdbc:oracle:thin:@localhost:1521:ORCL";
final String user = "store";
final String password = "store_password";
Class.forName("oracle.jdbc.driver.OracleDriver");
Connection con = DriverManager.getConnection(url, user, password);
return con;
} catch (ClassNotFoundException e) {
throw new SQLException(e.getMessage());
}
}
/**
* 物理分頁方法二針對mysql,使用sql語句的limit和offset來進行分頁
*
* @param currentPage
* 當前頁
* @param showRows
* 一頁顯示的數據量
*/
public List<Bars> pageListFour(int currentPage, int showRows) {
Connection con = null;
PreparedStatement ps = null;
ResultSet rs = null;
ArrayList<Bars> resultList = new ArrayList<Bars>();
try {
con = getConnection();
ps = con.prepareStatement(FIND_BARS_MYSQL);
//傳入參數,第一個參數表示顯示幾條記錄(limit關鍵字的含義)
ps.setInt(1, showRows);
//第二個參數表示丟棄幾條記錄(offset關鍵字的含義)
ps.setInt(2, showRows * (currentPage - 1));
rs = ps.executeQuery();
// 當返回結果集中有記錄時
while (rs.next()) {
Bars bar = new Bars();
bar.setId(rs.getLong("id"));
bar.setName(rs.getString("name"));
bar.setType(rs.getInt("type"));
bar.setCreatorId(rs.getLong("creator_id"));
resultList.add(bar);
}
} catch (SQLException e) {
e.printStackTrace();
} finally {
try {
if (rs != null)
rs.close();
if (ps != null)
ps.close();
if (con != null)
con.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
return resultList;
}
}
分頁結論:
1.物理分頁速度上並不一定快於邏輯分頁,邏輯分頁速度上也並不一定快於物理分頁。
2.物理分頁總是優於邏輯分頁:沒有必要將屬於數據庫端的壓力加諸到應用端來,就算速度上存在優勢,
然而其它性能上的優點足以彌補這個缺點。
3.在分頁工作前,有必要了解使用數據庫本身的一些sql語句特點更好的分頁。