项目实战--Spring Boot与PageHelper的集成及线程污染解决

一、PageHelper使用背景

公司要做个简单管理系统,要我搭建Spring Boot+MyBatis+PageHelper+Redis的项目框架然后交i给实习生来开发。这个其实很简单,但是遇到搭建和使用过程中PageHelper有好多小坑,就记录一下,避免再踩。

版本选择:

sql 复制代码
JDK 8
SpringBoot 2.5.0
MyBatis 3.5.7
PageHelper 5.2.0
二、步骤
2.1 新建Spring Boot项目

如果过程中,选择java版本时发现没有java8版本,只有java17和java21

原因:

sql 复制代码
spring2.X版本在2023年11月24日停止维护,因此创建spring项目时不再有2.X版本的选项,只能从3.1.X版本开始选择
而Spring3.X版本不支持JDK8,JDK11,最低支持JDK17,因此JDK11也无法选择

解决:

目前阿里云支持创建Spring2.X版本的项目

sql 复制代码
修改Server URL为:https://start.aliyun.com

这样就可以创建啦

2.2 引入依赖

在pom.xml文件中添加相关依赖:

xml 复制代码
<dependencies>
    <!-- Spring Boot Starter -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- MyBatis Starter -->
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.2.0</version>
    </dependency>
    <!-- PageHelper -->
    <dependency>
        <groupId>com.github.pagehelper</groupId>
        <artifactId>pagehelper-spring-boot-starter</artifactId>
        <version>1.4.0</version>
    </dependency>
</dependencies>
2.3 配置PageHelper

在application.yml文件中进行PageHelper的基本配置:

yaml 复制代码
pagehelper:
  helper-dialect: mysql # 指定数据库方言为MySQL
  reasonable: true # 分页合理化,启用后如果页码<1则查询第一页,页码>总页数则查询最后一页。
  support-methods-arguments: true # 支持通过Mapper接口参数来传递分页参数
  params: count=countSql # 指定count查询的参数名称
2.4 配置MyBatis

让PageHelper与MyBatis集成,还需在SpringBoot配置文件中添加MyBatis的相关配置:

yaml 复制代码
mybatis:
  mapper-locations: classpath:/mappers/*.xml # Mapper XML文件的位置
  type-aliases-package: com.example.demo.entity # 实体类的包路径
2.5 编写Mapper接口和XML

User实体类:

java 复制代码
public class User {
    private Long id;
    private String name;
    private String email;
    // getters and setters
}

对应的Mapper接口:

java 复制代码
public interface UserMapper {
    @Select("SELECT * FROM users")
    List<User> selectAll();
}

Mapper XML文件则包含分页查询的SQL:

yaml 复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.example.demo.mapper.UserMapper">
    <select id="selectAll" resultType="com.example.demo.entity.User">
        SELECT * FROM users
    </select>
</mapper>
2.6 使用PageHelper进行分页

Service层使用PageHelper进行分页查询:

java 复制代码
@Service
public class UserService {
    
    @Autowired
    private UserMapper userMapper;
    
    public PageInfo<User> getUsers(int pageNum, int pageSize) {
        PageHelper.startPage(pageNum, pageSize);
        List<User> users = userMapper.selectAll();
        return new PageInfo<>(users);
    }
}

在Controller层,通过RESTful接口来调用分页查询:

java 复制代码
@RestController
@RequestMapping("/users")
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping
    public PageInfo<User> getUsers(@RequestParam(defaultValue = "1") int pageNum,
                                   @RequestParam(defaultValue = "10") int pageSize) {
        return userService.getUsers(pageNum, pageSize);
    }
}

这就实现简单的分页查询功能,通过PageHelper来控制分页参数。

三、问题解决

(1)分页无效或查询结果为空

java 复制代码
确保在调用分页查询方法前,已经正确调用了PageHelper.startPage方法。
检查数据库连接是否正常,SQL查询语句是否正确。

(2)分页参数不生效

java 复制代码
检查Controller层是否正确接收并传递分页参数。
确保application.yml中配置的support-methods-arguments为true。

(3)性能问题

java 复制代码
对于大数据量的表,分页查询可能会带来性能问题。可以通过增加索引、优化SQL查询等方式提高性能。

(4)使用过程中线程污染,无缘故的分页

前端调用一个未分页的接口,出现数据丢失或者报错的情况:

现象:前端调用一个只查询一条数据的接口,该接口执行的SQL是:

sql 复制代码
select id,statistics_month,update_time from business_statistics_record
order by statistics_month desclimit 1

但是实际上,日记打印出来的SQL:limit 1 limit ?, ? ;就出现查询异常:

经过排查,真正原因是因为调用自定义分页出现问题:PageHelper.startPage(pageNum, pageSize);调用之后并没有消费,分页参数一直保存在线程中,当这个线程再次调用的时候,导致莫名奇妙的加上limit关键字。

查看PageHelper源码看到:

sql 复制代码
PageHelper 方法使用静态的 ThreadLocal参数,分页参数和线程是绑定的。只要保证在 PageHelper方法调用后紧跟MyBatis查询方法,这就是安全的。因为 PageHelper在finally代码段中自动清除ThreadLocal存储的对象。

而随机加上limit关键字,查看ThreadLocal LOCAL_PAGE值的变化,只有当线程复用的时候才会出现LOCAL_PAGE已被实例化。

为避免使用PageHelper过程中如果出现无缘无故出现分页,

在使用了PageHelper.startPage()后需要紧接着 MyBatis 查询方法。

最好是在执行sql的方法加上finally语句清理page缓存:

这个afterAll()方法中:

而 clearPage()方法的功能是:

相关推荐
Q_1928499906几秒前
基于Spring Boot的摄影器材租赁回收系统
java·spring boot·后端
Code_流苏3 分钟前
VSCode搭建Java开发环境 2024保姆级安装教程(Java环境搭建+VSCode安装+运行测试+背景图设置)
java·ide·vscode·搭建·java开发环境
良许Linux5 分钟前
0.96寸OLED显示屏详解
linux·服务器·后端·互联网
求知若饥17 分钟前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
禁默1 小时前
深入浅出:AWT的基本组件及其应用
java·开发语言·界面编程
Cachel wood1 小时前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
Code哈哈笑1 小时前
【Java 学习】深度剖析Java多态:从向上转型到向下转型,解锁动态绑定的奥秘,让代码更优雅灵活
java·开发语言·学习
gb42152871 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot