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

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.dtcms.com/a/355578.html

相关文章:

  • CPU、进程、线程上下文切换
  • 使用Rag 命中用户feedback提升triage agent 准确率
  • 特斯拉 Tesla FSD 12.6.4,FSD14, VLA 和 华为 ADS 4.0 比较
  • 广东省省考备考(第八十九天8.28)——判断推理(第九节课)
  • 智能客服多智能体(知识库问答+情绪感知+工单路由)
  • 玄机靶场 | 第九章-blueteam 的小心思3
  • openEuler中LVM调整实现home与root分区空间平衡
  • LeetCode 3446. 按对角线进行矩阵排序
  • Linux部分底层机制
  • 【每天一个知识点】云存储(Cloud Storage)
  • 从混沌到有序:工作流设计的创作迷思与破局之道
  • AI智能农业监测系统深度解读:从大田作物管理到病虫害预警,破解传统农业增产难题
  • 大模型私有化部署
  • RAG概念被误用:AI应用落地需回归上下文工程本质
  • leetcode算法day22
  • kotlin中关于协程的使用
  • 陕西风味马卡龙:家常自制,特色甜趣共享
  • 传输层协议介绍
  • 结构化提示词革命:JSON Prompting如何让AI输出精准如激光
  • 数字化生产管理系统 (MES)
  • 服务器核心组件:CPU 与 GPU 的核心区别、应用场景、协同工作
  • 揭开.NET Core 中 ToList () 与 ToArray () 的面纱:从原理到抉择
  • ansible常用命令的简单练习
  • Linux系统 -- 多线程的控制(互斥与同步)
  • 数学思维好题(冯哈伯公式)-平方根下取整求和
  • 个人博客运行3个月记录
  • 了解ADS中信号和电源完整性的S参数因果关系
  • Typora 教程:从零开始掌握 Markdown 高效写作
  • MySQL事务ACID特性
  • JavaScript中的BOM,DOM和事件