当前位置: 首页 > backend >正文

Java学习笔记之——通过分页查询样例感受JDBC、Mybatis以及MybatisPlus(一)

该系列通过使用JDBC、Mybatis、MybatisPlus 三种方式分别实现分页查询,顺便更直观的感受后两者框架的便利性。

代码仓库:https://gitee.com/strivezhangp/spring-mysql-demo.git (该系列将在该仓库更新!!!)

分页查询就是将大量数据分成多个"页面"来显示,而不是一次性加载所有数据。就像看书一样,一页一页地翻阅,而不是把整本书的内容都摊开在面前。

【资源分享】

在学习数据库的时候,没有合适的数据可以参考MySQL官网提供的样例数据,如:https://dev.mysql.com/doc/index-other.html

(其中是一个Word的数据库,三张表格,包含了各个国家的一些城市,数据量中等,可以用来参考学习)

1 分页查询的两种实现方式

客户端分页(不推荐)

原理:一次性从数据库查询所有数据,然后在应用程序内存中进行分页

缺点:数据量大时性能极差,占用大量内存

示例SELECT * FROM city→ 在Java中手动分页

服务端分页(推荐)

原理:数据库只返回当前页需要的数据

优点:性能好,节省内存和网络带宽

示例SELECT * FROM city LIMIT 0, 10→ 只返回前10条数据

2 一个分页请求需要传递哪些数据

一般来说,常用的web请求,服务端拥有以下参数需要接收和返回。

private Integer pageNum;     // 当前页码
private Integer pageSize;    // 每页大小
private Long totalCount;     // 总记录数
private Integer totalPages;  // 总页数
private List<T> data;        // 当前页数据列表

3 通过最基础的JDBC实现一个分页查询

3.1 JDBC建立数据库连接

JDBC驱动数据库的准备工作主要分为以下步骤实现:

1、 创建一个数据库工具类 DatabaseUtil 类

​ 【下图是一个自建的工具类中部分方法,绿框中是静态的字段 包含了数据库信息; 红色框中则是封装了一些必备的方法】

在这里插入图片描述
2、 创建数据表中对应的实体类 City

3、 在Dao层借助工具类 DatabaseUtil 类实现分页查询功能

4、 最后在 controller 中通过Spring Web接口调用 测试分页查询的结果

3.2 JDBC实现基本查询的代码实现

首先,借助JDBC实现一个简单的数据的查询 select * from city limit 50

通过这个查询的实现,了解 JDBC 对数据操作主要的步骤如下:

1、 连接数据库 connection(获取数据库连接)

2、 创建自己的SQL语句 此处使用了条件限制,查询前50条数据

3、 创建 Statement对象,对SQL语句进行预编译

4、 执行SQL语句,并返回到结果集 ResultSet 中

5、 从结果集 ResultSet 中获取查询信息,并返回

6、 关闭全部资源(数据库连接、结果集等)

/*** 查询城市数据列表*/
public List<City> listCity() {List<City> cityList = new ArrayList<>();// 连接数据库前相关信息的声明Connection connection = null; // 数据库连接的声明PreparedStatement statement = null; // 执行预编译的SQL语句 比较Statement 更安全ResultSet resultSet = null; // 结果集try {// 获取数据库连接connection = DatabaseUtil.getConnection();// 创建SQL语句String sql = "select * from city limit 50";// 创建执行SQL语句的Statement对象statement = connection.prepareStatement(sql);// 执行SQL语句resultSet = statement.executeQuery();// 遍历结果集while (resultSet.next()) {City city = new City();city.setId(resultSet.getInt("id"));city.setName(resultSet.getString("name"));city.setCountryCode(resultSet.getString("countrycode"));city.setDistrict(resultSet.getString("district"));city.setPopulation(resultSet.getInt("population"));cityList.add(city);}logger.info("-------------SQL语句:{}, 共查询数据 {} 行--------------", sql, cityList.size());} catch (SQLException e) {e.printStackTrace();} finally {// 关闭数据库连接DatabaseUtil.closeAll(connection, statement, resultSet);}return cityList;
}

3.3 JDBC实现简单的分页查询

3.3.1 分页查询的实现

首先,实现一个拥有分页效果的查询 通过采用了SQL语句中的 limit条件限制,如下:

limit 拥有两个数据,一个是起始的位置,一个是从起始位置开始往后的数据量

/*** 实现城市信息的分页查询* @param pageNum 页码* @param pageSize 每页的数据量*/
public List<City> listCityByPage(int pageNum, int pageSize) {List<City> cityList = new ArrayList<>();Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;try {// 获取数据库连接connection = DatabaseUtil.getConnection();// 手动计算分页查询的起始位置 当前页码-1 乘以 每页的记录数int start = (pageNum - 1) * pageSize;// 拼接SQL语句 实现分页查询String sql = "select * from city limit ?, ?";statement = connection.prepareStatement(sql);statement.setInt(1, start); // 添加limit条件的起点位置statement.setInt(2, pageSize); // 添加limit条件的数据量// 获取结果resultSet = statement.executeQuery();// 遍历结果集while (resultSet.next()) {City city = new City();city.setId(resultSet.getInt("id"));city.setName(resultSet.getString("name"));city.setCountryCode(resultSet.getString("countrycode"));city.setDistrict(resultSet.getString("district"));city.setPopulation(resultSet.getInt("population"));cityList.add(city);}} catch (SQLException e) {throw new RuntimeException(e);}finally {DatabaseUtil.closeAll(connection, statement, resultSet);}return cityList;
}

3.3.2 获取每页的数据量

【思考】

只有上述的条件限制查询是不够的,因为分页查询应当返回当前表的 总数据量 用来计算 共多少页 数据 。

例如:共400页 / 第 10 页。因此,还需要两个方法来进一步实现。

  • **获取整个标的总数据量 **select count(*) from city
  • 获取根据每页数据量要求能分为几个页面

所以,添加两个方法如下:

/*** 获取总的记录条数*/
public int getTotalCount() {Connection connection = null;PreparedStatement statement = null;ResultSet resultSet = null;int totalCount = 0;try {connection = DatabaseUtil.getConnection();String sql = "select count(*) from city";statement = connection.prepareStatement(sql);resultSet = statement.executeQuery();if (resultSet.next()) {totalCount = resultSet.getInt(1);}}catch (SQLException e) {throw new RuntimeException(e);}finally {DatabaseUtil.closeAll(connection, statement, resultSet);}return totalCount;
}/*** 获取总页数*/
public int getTotalPage(int pageSize) {int totalCount = getTotalCount();// 借助了Math.ceil()方法 达到向上取整的效果 如:3.2 取 4return (int)Math.ceil((double) totalCount / pageSize);
}

3.3.3 分页查询的最终实现

service层方法

/*** 分页查询城市数据*/
public Map<String, Object> listCityByPage(int pageNum, int pageSize) {// 查询当前页数据List<City> cityList = cityDao.listCityByPage(pageNum, pageSize);// 获取总页数和总数据流long totalCount = cityDao.getTotalCount();int totalPage = cityDao.getTotalPage(pageSize);// 构建返回的结果Map<String, Object> result = new HashMap<>();result.put("pageNum", pageNum);result.put("pageSize", pageSize);result.put("totalCount", totalCount);result.put("totalPage", totalPage);result.put("list", cityList);return result;
}

controller层调用

/*** 城市信息分页查询*/
@GetMapping("/getCityListByPage")
public Map<String, Object> getCityListByPage(@RequestParam Integer pageNum, @RequestParam Integer pageSize) {return cityService.listCityByPage(pageNum, pageSize);
}

3.3.4 结果展示

在这里插入图片描述

4 思考

关于JDBC这个样例,后续有以下的改进空间:

1、规范化返回接口的信息,可以专门封装一个 PageResult 实体类,用来查询并返回分页结果;

2、返回的分页查询结构考虑进一步实现序列化;

3、如何更灵活的实现一个带条件的分页查询?

5 改进

5.1 规范接口信息的 PageResult 实体类的封装

通过结合泛型,实现分页查询结果的封装,定义为一个新类,主要的优点如下:

  • 标准化响应结构:保持所有分页接口返回格式统一
  • 前端友好:提供完整的分页导航信息
  • 易于扩展:方便后续添加其他分页相关字段
  • 减少重复代码:避免在每个接口中重复定义分页字段

具体的实现很简单,见下代码:

public class PageResult<T> {private Integer pageNum;private Integer pageSize;private Long total;private Integer totalPages;private List<T> data; // 采用泛型来接受不定类型的数据// 构造方法以及getter与setter方法省略
}

5.1.1 结合分页结果实体类实现分页查询

在这里插入图片描述
在这里插入图片描述

5.2 补充JDBC动态条件分页查询

整体的思路如下:

city数据库中的字段有 Name、ContryCode、District以及Population,如何实现多条件查询?

如:即有可能根据Name查询,也有可能根据ContryCode进行查询,还有需要根据人口数量进行排序查询

【重点问题】

传统的JDBC方式的SQL语句 如何实现动态的拼接?

该问题的解决思路如下:

可以借助StringBuilder对SQL语句进行一个拼接,如何将参数判断那个字段为查询条件,进一步借助了map中的 containsKey方法来对条件进行筛选。

/**
* 如:name=beijing 这个条件传入,将name放到map中,map中如果有这个字段,就进一步将 beijing 值放入paramslist中,等待最终的sql拼接。
**/// StringBuilder 动态构建SQL
StringBuilder sql = new StringBuilder("SELECT * FROM city WHERE 1=1");
List<Object> params = new ArrayList<>();  // List存放具体的参数值// map 动态存放条件查询的字段
Map<String, Object> conditions = new HashMap<>();

5.2.1 具体的代码实现

(1)Dao层添加代码

两个方法

  • 查询符合条件的城市信息的方法(主要作用是拼接sql条件,实现查询)
  • 统计符合条件的城市信息总条数(主要作用是方便返回符合条件的总记录数以及总页数的计算)

具体的代码见下:

/*** 带动态条件的分页查询*/
public List<City> getCitiesByPageWithConditions(int pageNum, int pageSize, Map<String, Object> conditions) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;List<City> cities = new ArrayList<>();try {connection = DatabaseUtil.getConnection();int start = (pageNum - 1) * pageSize;// StringBuilder 动态构建SQLStringBuilder sql = new StringBuilder("SELECT * FROM city WHERE 1=1");List<Object> params = new ArrayList<>();// 判断是否有城市名字段作为条件,有的话补全like查询的sql语句,将其值放入sql占位符if (conditions.containsKey("nameLike")) {sql.append(" AND Name LIKE ?");params.add("%" + conditions.get("nameLike") + "%");}// 添加最小的人口字段作为条件 同上原理if (conditions.containsKey("minPopulation")) {sql.append(" AND Population >= ?");params.add(conditions.get("minPopulation"));}// 最终结果按照人口数量降序sql.append(" ORDER BY Population DESC LIMIT ?, ?");// 添加limit条件params.add(start);params.add(pageSize);preparedStatement = connection.prepareStatement(sql.toString());// 设置参数for (int i = 0; i < params.size(); i++) {preparedStatement.setObject(i + 1, params.get(i));}resultSet = preparedStatement.executeQuery();while (resultSet.next()) {City city = new City();city.setId(resultSet.getInt("ID"));city.setName(resultSet.getString("Name"));city.setCountryCode(resultSet.getString("CountryCode"));city.setDistrict(resultSet.getString("District"));city.setPopulation(resultSet.getInt("Population"));cities.add(city);}} catch (SQLException e) {e.printStackTrace();} finally {DatabaseUtil.closeAll(connection, preparedStatement, resultSet);}return cities;
}/*** 获取带条件的总记录数*/
public long getCountByConditions(Map<String, Object> conditions) {Connection connection = null;PreparedStatement preparedStatement = null;ResultSet resultSet = null;long count = 0;try {connection = DatabaseUtil.getConnection();StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM city WHERE 1=1");List<Object> params = new ArrayList<>();// 同样的条件逻辑if (conditions.containsKey("nameLike")) {sql.append(" AND Name LIKE ?");params.add("%" + conditions.get("nameLike") + "%");}// 添加最小的人口字段作为条件 同上原理if (conditions.containsKey("minPopulation")) {sql.append(" AND Population >= ?");params.add(conditions.get("minPopulation"));}// ... 其他条件preparedStatement = connection.prepareStatement(sql.toString());for (int i = 0; i < params.size(); i++) {preparedStatement.setObject(i + 1, params.get(i));}resultSet = preparedStatement.executeQuery();if (resultSet.next()) {count = resultSet.getLong(1);}} catch (SQLException e) {e.printStackTrace();} finally {DatabaseUtil.closeAll(connection, preparedStatement, resultSet);}return count;
}
(2)Service层添加代码

主要是调用dao层代码进一步规范化返回结果,条件分页查询

/*** 动态条件分页查询*/
public PageResult<City> getCitiesByPageWithConditions(int pageNum, int pageSize, Map<String, Object> conditions) {// 获取分页结果List<City> cities = cityDao.getCitiesByPageWithConditions(pageNum, pageSize, conditions);// 获取符合条件的总记录条数long totalCount = cityDao.getCountByConditions(conditions);// 获取总页数int totalPages = (int) Math.ceil((double) totalCount / pageSize);return new PageResult<>(pageNum, pageSize, totalCount, totalPages, cities);
}
(3)Controller层代码

通过收集传递的条件信息,拼装成map形式,进一步调用service层方法

/*** 带动态条件的分页查询*/
@GetMapping("/getCitiesListByPageWithConditions")
public PageResult<City> getCitiesListByPageWithConditions(@RequestParam Integer pageNum,@RequestParam Integer pageSize,@RequestParam(required = false) String name,@RequestParam(required = false) Integer minPopulation
) {// 构建查询条件的 mapMap<String, Object> conditions = new HashMap<>();if (name != null) conditions.put("nameLike" , name);if (minPopulation != null) conditions.put("minPopulation", minPopulation);return cityService.getCitiesByPageWithConditions(pageNum, pageSize, conditions);
}

5.2.2 图示演示整体思路

实现的基本步骤通过调试演示如下:
在这里插入图片描述

5.2.3 结果展示

在这里插入图片描述

http://www.xdnf.cn/news/18899.html

相关文章:

  • 上海控安:汽车API安全-风险与防护策略解析
  • Java 实现HTML转Word:从HTML文件与字符串到可编辑Word文档
  • Nginx + Certbot配置 HTTPS / SSL 证书(简化版已测试)
  • 机器视觉学习-day07-图像镜像旋转
  • 【Deepseek】Windows MFC/Win32 常用核心 API 汇总
  • 【PyTorch】基于YOLO的多目标检测项目(一)
  • 【Redis】数据分片机制和集群机制
  • 【Java SE】基于多态与接口实现图书管理系统:从设计到编码全解析
  • C/C++---前缀和(Prefix Sum)
  • 微服务的编程测评系统17-判题功能-代码沙箱
  • MQTT broker 安装与基础配置实战指南(一)
  • 题目—移除元素
  • PyTorch中的激活函数
  • AI需求优先级:数据价值密度×算法成熟度
  • HSA35NV001美光固态闪存NQ482NQ470
  • 达可替尼-
  • SpringBoot整合RabbitMQ:从消息队列基础到高可用架构实战指南
  • 浏览器网页路径扫描器(脚本)
  • 改造thinkphp6的命令行工具和分批次导出大量数据
  • MySQL 基础:DDL、DML、DQL、DCL 四大类 SQL 语句全解析
  • K8s 二次开发漫游录
  • 了解CDC(变更数据捕获)如何革新数据集成方式
  • Spring Security 深度学习(一): 基础入门与默认行为分析
  • 【日常学习】2025-8-27 测开框架设计模式探索04
  • Elasticsearch数据迁移快照方案初探(二):快照创建与多节点存储问题解决
  • 数据结构:创建堆(或者叫“堆化”,Heapify)
  • 软件定义汽车(SDV)调试——如何做到 适配软件定义汽车(SDV)?(中)
  • Mysql数据挂载
  • TencentOS Server 4.4 下创建mysql容器无法正常运行的问题
  • 微服务-docker compose