# JavaWeb分頁查詢功能怎么實現
## 一、分頁查詢概述
### 1.1 什么是分頁查詢
分頁查詢是指在數據庫查詢時將大量數據分割成多個部分進行展示的技術。當數據量達到成千上萬條時,一次性加載所有數據會導致:
1. 服務器內存壓力劇增
2. 網絡傳輸時間過長
3. 客戶端渲染性能下降
4. 用戶體驗差(頁面卡頓、滾動條過長)
分頁查詢通過"每次只加載部分數據"的方式解決這些問題,是現代Web應用的標配功能。
### 1.2 分頁查詢的應用場景
- 電商平臺商品列表(如每頁顯示20件商品)
- 后臺管理系統數據展示(用戶列表、訂單記錄)
- 內容管理系統(新聞列表、博客文章)
- 社交平臺動態信息流
## 二、分頁技術實現方案
### 2.1 前端分頁 vs 后端分頁
| 對比項 | 前端分頁 | 后端分頁 |
|--------------|----------------------------|----------------------------|
| 數據處理位置 | 瀏覽器內存 | 數據庫服務器 |
| 傳輸數據量 | 一次性傳輸全部數據 | 只傳輸當前頁數據 |
| 適用場景 | 數據量小(<1000條) | 數據量大(>1000條) |
| 實現復雜度 | 簡單(純JS實現) | 較高(需前后端協作) |
| 典型實現 | DataTables插件 | SQL的LIMIT/OFFSET |
**結論**:JavaWeb項目通常采用后端分頁方案。
### 2.2 后端分頁核心要素
1. **頁碼(pageNum)**:當前請求的頁數
2. **每頁條數(pageSize)**:單頁顯示的數據量
3. **總記錄數(total)**:滿足條件的全部數據量
4. **總頁數(pages)**:根據總記錄數和每頁條數計算得出
## 三、MySQL數據庫分頁實現
### 3.1 LIMIT子句分頁
```sql
-- 基本語法
SELECT * FROM table_name LIMIT offset, pageSize;
-- 示例:查詢第2頁,每頁10條(偏移量10)
SELECT * FROM products LIMIT 10, 10;
-- 原始查詢(性能差)
SELECT * FROM large_table LIMIT 100000, 10;
-- 優化后(先查ID再關聯)
SELECT t.* FROM large_table t
JOIN (SELECT id FROM large_table LIMIT 100000, 10) tmp
ON t.id = tmp.id;
-- 確保查詢字段被索引覆蓋
ALTER TABLE products ADD INDEX idx_category_status(category_id, status);
-- 使用覆蓋索引查詢
SELECT id, name FROM products
WHERE category_id = 5 AND status = 1
LIMIT 10000, 10;
public class PageRequest {
private int pageNum = 1; // 默認第一頁
private int pageSize = 10; // 默認每頁10條
// getters & setters
}
public class PageResult<T> {
private int pageNum;
private int pageSize;
private long total;
private int pages;
private List<T> data;
// 計算總頁數
public int getPages() {
return (int) Math.ceil((double) total / pageSize);
}
}
public interface ProductMapper {
// 查詢分頁數據
List<Product> selectByPage(@Param("offset") int offset,
@Param("pageSize") int pageSize);
// 查詢總數
long selectCount();
}
<select id="selectByPage" resultType="Product">
SELECT * FROM products
ORDER BY create_time DESC
LIMIT #{offset}, #{pageSize}
</select>
<select id="selectCount" resultType="long">
SELECT COUNT(*) FROM products
</select>
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.2</version>
</dependency>
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
public PageResult<Product> queryByPage(int pageNum, int pageSize) {
// 開啟分頁
PageHelper.startPage(pageNum, pageSize);
// 查詢數據(會自動分頁)
List<Product> products = productMapper.selectAll();
// 封裝分頁結果
PageInfo<Product> pageInfo = new PageInfo<>(products);
return new PageResult<>(
pageInfo.getPageNum(),
pageInfo.getPageSize(),
pageInfo.getTotal(),
pageInfo.getPages(),
pageInfo.getList()
);
}
}
<!-- 分頁導航 -->
<div class="pagination">
<c:if test="${pageResult.pageNum > 1}">
<a href="?pageNum=1">首頁</a>
<a href="?pageNum=${pageResult.pageNum-1}">上一頁</a>
</c:if>
<c:forEach begin="1" end="${pageResult.pages}" var="i">
<c:choose>
<c:when test="${i == pageResult.pageNum}">
<span class="current">${i}</span>
</c:when>
<c:otherwise>
<a href="?pageNum=${i}">${i}</a>
</c:otherwise>
</c:choose>
</c:forEach>
<c:if test="${pageResult.pageNum < pageResult.pages}">
<a href="?pageNum=${pageResult.pageNum+1}">下一頁</a>
<a href="?pageNum=${pageResult.pages}">末頁</a>
</c:if>
</div>
<template>
<div>
<el-table :data="tableData">
<!-- 表格列定義 -->
</el-table>
<el-pagination
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
:current-page="pageNum"
:page-sizes="[10, 20, 50, 100]"
:page-size="pageSize"
layout="total, sizes, prev, pager, next, jumper"
:total="total">
</el-pagination>
</div>
</template>
<script>
export default {
data() {
return {
tableData: [],
pageNum: 1,
pageSize: 10,
total: 0
}
},
methods: {
loadData() {
axios.get('/api/products', {
params: {
pageNum: this.pageNum,
pageSize: this.pageSize
}
}).then(response => {
this.tableData = response.data.list
this.total = response.data.total
})
},
handleSizeChange(val) {
this.pageSize = val
this.loadData()
},
handleCurrentChange(val) {
this.pageNum = val
this.loadData()
}
},
created() {
this.loadData()
}
}
</script>
LIMIT 100000,10
需要掃描前100010條記錄-- 第一頁
SELECT * FROM orders
WHERE create_time > '2023-01-01'
ORDER BY id ASC
LIMIT 10;
-- 后續頁(使用上一頁最后一條記錄的ID)
SELECT * FROM orders
WHERE id > 上一頁最后ID AND create_time > '2023-01-01'
ORDER BY id ASC
LIMIT 10;
// 方案1:緩存總數(適合不要求精確的場景)
@Cacheable(value = "productCount")
public long getProductCount() {
return productMapper.selectCount();
}
// 方案2:使用EXPLN估算(誤差約±5%)
public long estimateCount() {
return productMapper.estimateCount();
}
當單表數據超過500萬時考慮分庫分表: - 水平分表:按時間范圍或ID哈希拆分 - 垂直分表:將大字段拆分到單獨表
public interface ProductRepository extends JpaRepository<Product, Long> {
@Query("SELECT p FROM Product p WHERE p.category = :category")
Page<Product> findByCategory(@Param("category") String category,
Pageable pageable);
}
// 服務層調用
public Page<Product> getProducts(int page, int size) {
PageRequest pageable = PageRequest.of(page - 1, size,
Sort.by("createTime").descending());
return productRepository.findByCategory("electronics", pageable);
}
@Repository
public class ProductCustomRepository {
@PersistenceContext
private EntityManager em;
public Page<Product> customQuery(String keyword, Pageable pageable) {
// 查詢總數
Long total = em.createQuery("SELECT COUNT(p) FROM Product p WHERE p.name LIKE :keyword", Long.class)
.setParameter("keyword", "%"+keyword+"%")
.getSingleResult();
// 查詢數據
List<Product> content = em.createQuery("SELECT p FROM Product p WHERE p.name LIKE :keyword", Product.class)
.setParameter("keyword", "%"+keyword+"%")
.setFirstResult((int) pageable.getOffset())
.setMaxResults(pageable.getPageSize())
.getResultList();
return new PageImpl<>(content, pageable, total);
}
}
@Test
public void testPagination() {
// 正常情況
testPageQuery(1, 10, 200, 20);
// 邊界情況
testPageQuery(0, 10, 200, 20); // 頁碼修正為1
testPageQuery(21, 10, 200, 20); // 超出范圍返回空列表
// 特殊參數
testPageQuery(1, -10, 200, 20); // 頁大小修正為默認值
testPageQuery(1, 1000, 200, 1); // 最大限制100條
}
private void testPageQuery(int pageNum, int pageSize, int total, int expectedPages) {
PageRequest request = new PageRequest(pageNum, pageSize);
PageResult<?> result = service.queryByPage(request);
assertEquals(expectedPages, result.getPages());
assertTrue(result.getData().size() <= Math.min(pageSize, 100));
}
@ControllerAdvice
public class GlobalExceptionHandler {
@ExceptionHandler(IllegalPageRequestException.class)
public ResponseEntity<ErrorResponse> handlePageException(IllegalPageRequestException ex) {
ErrorResponse error = new ErrorResponse(
"PAGE_PARAM_ERROR",
"分頁參數不合法: " + ex.getMessage(),
System.currentTimeMillis()
);
return ResponseEntity.badRequest().body(error);
}
}
// 自定義異常
public class IllegalPageRequestException extends RuntimeException {
public IllegalPageRequestException(String message) {
super(message);
}
}
public class PageRequest {
private String sortField = "id"; // 默認排序字段
private Sort.Direction sortDirection = Sort.Direction.DESC; // 默認降序
// 轉換為Spring的Sort對象
public Sort getSort() {
return Sort.by(sortDirection, sortField);
}
}
// 前端傳參格式
// ?pageNum=1&pageSize=10&sortField=price&sortDirection=asc
public PageResult<Product> searchProducts(ProductQuery query) {
// 構建動態查詢條件
QProduct qProduct = QProduct.product;
BooleanBuilder builder = new BooleanBuilder();
if (StringUtils.isNotBlank(query.getKeyword())) {
builder.and(qProduct.name.contains(query.getKeyword()));
}
if (query.getMinPrice() != null) {
builder.and(qProduct.price.goe(query.getMinPrice()));
}
// 其他條件...
// 執行分頁查詢
Pageable pageable = PageRequest.of(
query.getPageNum() - 1,
query.getPageSize(),
query.getSort()
);
Page<Product> page = productRepository.findAll(builder, pageable);
return convertToPageResult(page);
}
通過本文的詳細講解,相信您已經掌握了JavaWeb分頁查詢的完整實現方案。實際開發中應根據項目需求選擇最適合的技術組合,并持續關注分頁性能優化。 “`
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。