项目实战--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()方法的功能是:

相关推荐
南宫生1 小时前
力扣每日一题【算法学习day.132】
java·学习·算法·leetcode
计算机毕设定制辅导-无忧学长2 小时前
Maven 基础环境搭建与配置(一)
java·maven
bing_1582 小时前
简单工厂模式 (Simple Factory Pattern) 在Spring Boot 中的应用
spring boot·后端·简单工厂模式
天上掉下来个程小白3 小时前
案例-14.文件上传-简介
数据库·spring boot·后端·mybatis·状态模式
风与沙的较量丶3 小时前
Java中的局部变量和成员变量在内存中的位置
java·开发语言
m0_748251723 小时前
SpringBoot3 升级介绍
java
Asthenia04123 小时前
基于Jackson注解的JSON工具封装与Redis集成实战
后端
编程星空3 小时前
css主题色修改后会多出一个css吗?css怎么定义变量?
开发语言·后端·rust
极客先躯4 小时前
说说高级java每日一道面试题-2025年2月13日-数据库篇-请说说 MySQL 数据库的锁 ?
java·数据库·mysql·数据库的锁·模式分·粒度分·属性分
程序员侠客行4 小时前
Spring事务原理 二
java·后端·spring