您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# 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进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。