前置

        在我参加工作的早期,分页查询都是通过手动编写SQL来控制,效率相对较差。Mysql数据库分页使用LIMIT关键字,Oracle的分页很难写,既复杂又难以记忆。后来公司的大佬提出这类分页可以使用分页插件来做。

一、配置分页插件

       (1)先导入依赖。

         这个分页插件是属于pageHelper的系列,现在因为SpringBoot的流行,他们公司也主动做了对SpringBoot的集成。

<!-- 分页工具pageHelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.10</version>
</dependency>

         (2)在yml中加入分页配置。这里指明是mysql数据库。配置部分搭建完毕。

spring:
pagehelper:
#指明数据库
helperDialect: mysql
# when pageNum<=0 ,query first page
# when pageNum > max count , query last page
reasonable: true
supportMethodsArguments: true
params: count=countSql

二、编写拦截器

         分页插件提供的主要类叫做: PageRowBounds。

         为了了解常见的分页参数,我特地去了前端框架ExtJs官网看了一下他们的分页传的参数。

         一般分页传递的参数是 start  、page 、 limit。

         

第十一节  SpringBoot使用Mybatis分页插件_spring

         因此,我们编写如下的拦截器,拦截住前端的分页相关参数。

         根据这些分页参数,构造出分页插件需要的PageRowBounds对象。

         然后再把这个PageRowBounds对象存放到当前线程中,这里使用到了ThreadLocal技术。

         ThreadLocal技术的原理就是,把要被存放的数据作为"线程局部变量"存放到线程中。

          以后不管这个线程执行到哪里,都能够取到 "线程局部变量"。

          关于ThreadLocal,我弄个示意图。更多关于ThreadLocal的知识,请参考多线程相关技术。

          

第十一节  SpringBoot使用Mybatis分页插件_局部变量_02

          最后当请求执行完毕后,再用拦截器的后处理PostHandler,清除掉"线程局部变量"。

        (1)编写分页拦截器。

package com.zhoutianyu.learnspringboot.interceptor;


import com.github.pagehelper.PageRowBounds;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.bind.ServletRequestUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

@Component
public class PageHelperInterceptor extends HandlerInterceptorAdapter {

private static final Logger LOGGER = LoggerFactory.getLogger(PageHelperInterceptor.class);

private static final String PAGE = "page";
private static final String LIMIT = "limit";
private static final int DEFAULT_PAGE_INDEX = 0;
private static final int DEFAULT_PAGE_SIZE = 10;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

//原生的int类型可能会有空指针异常,无法将null赋值给基本数据类型
Integer page = ServletRequestUtils.getIntParameter(request, PAGE);
Integer limit = ServletRequestUtils.getIntParameter(request, LIMIT);
if (null == page || page < 1) {
page = DEFAULT_PAGE_INDEX;
} else {
page = page - 1;
}
if (null == limit || limit < 0) {
limit = DEFAULT_PAGE_SIZE;
}
int start = page * limit;

LOGGER.info("分页资源预处理");
LOGGER.info("从第{}条记录开始查询,共查询{}条记录", start + 1, limit);

//generate an page object and put it into threadLocal
PageRowBounds pageRowBounds = new PageRowBounds(start, limit);
PageHelperThreadLocal.setPageInfo(pageRowBounds);
return true;
}

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
//when the business process has been completed,
//removing page object which is in the threadLocal
PageHelperThreadLocal.clean();
}

}

       (2)编写ThreadLocal,用于存放线程局部变量 

package com.zhoutianyu.learnspringboot.interceptor;


import com.github.pagehelper.PageRowBounds;

public final class PageHelperThreadLocal {

private static final ThreadLocal<PageRowBounds> PAGE_INFO = new ThreadLocal<>();

/**
* 为调用的线程存储 线程局部变量 PageRowBounds(分页信息封装在此对象里)
*/
public static void setPageInfo(PageRowBounds rowBounds) {
PAGE_INFO.set(rowBounds);
}

/**
* 获取调用此方法的线程的 线程局部变量 PageRowBounds
*/
public static PageRowBounds getPageInfo() {
return PAGE_INFO.get();
}

/**
* 清除掉当前线程中的线程局部变量
*/
public static void clean() {
PAGE_INFO.remove();
}
}

       (3)将拦截器配置到项目中。

package com.zhoutianyu.learnspringboot.config;

import com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter;
import com.zhoutianyu.learnspringboot.interceptor.MyInterceptor;
import com.zhoutianyu.learnspringboot.interceptor.PageHelperInterceptor;
import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

import javax.annotation.Resource;

@Configuration
public class CustomWebConfigure implements WebMvcConfigurer {

@Resource
private PageHelperInterceptor pageHelperInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(pageHelperInterceptor).addPathPatterns("/**").excludePathPatterns("/static/**");
}
}

三、调用分页工具

         编写Controller、Mapper、mapper.xml进行测试。

         重点需要关注的是如何使用分页插件。

         在Dao层查询的方法上,一定要将分页对象PageRowBounds作为方法的参数,才能使用我们的分页插件。

package com.zhoutianyu.learnspringboot.mybatis;

import lombok.Data;

@Data
public class User {

private Long id;

private String username;

private Integer age;
}
package com.zhoutianyu.learnspringboot.mybatis;

import com.zhoutianyu.learnspringboot.interceptor.PageHelperThreadLocal;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.List;

@RestController
public class UserController {

private static final Logger LOGGER = LoggerFactory.getLogger(UserController.class);

@Autowired
private UserMapper mapper;

@GetMapping(value = "mybatis/getUsers")
public List<User> getUsers() {
return mapper.getUsers(PageHelperThreadLocal.getPageInfo());
}
}

         下面的代码展示了如何使用分页插件。

package com.zhoutianyu.learnspringboot.mybatis;

import com.github.pagehelper.PageRowBounds;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;

@Mapper
public interface UserMapper {

List<User> getUsers(PageRowBounds pageRowBounds);
}
<?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.zhoutianyu.learnspringboot.mybatis.UserMapper">
<resultMap id="BaseResultMap" type="com.zhoutianyu.learnspringboot.mybatis.User">

<id column="id" jdbcType="BIGINT" property="id"/>
<result column="username" jdbcType="VARCHAR" property="username"/>
<result column="age" jdbcType="INTEGER" property="age"/>
</resultMap>
<sql id="Base_Column_List">
id, username, age
</sql>

<select id="getUsers" resultType="com.zhoutianyu.learnspringboot.mybatis.User">
select
<include refid="Base_Column_List"/>
from t_user
</select>
</mapper>

        数据库

CREATE TABLE `t_user` (
`id` bigint(11) NOT NULL AUTO_INCREMENT,
`username` varchar(255) DEFAULT NULL,
`age` int(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

第十一节  SpringBoot使用Mybatis分页插件_spring_03

四、测试

       如下动图,实际的getUsers在XML中,并没有写分页语句,所有的分页由分页插件帮我们做。

       ​​http://localhost:8081/study/springboot/mybatis/getUsers?page=2&limit=2​​等

       通过SQL监控可以看出,分页插件实际上还先帮我们查了一次个数,这完全替代了以前的重复劳动。

       当然了,在实际使用过程中还需要考虑数据库的数据量。如果某张表的数据量很大,动辄三四十万条记录,手动分页还是有必要的。因此,分表在数据库设计过程也是十分重要的。对于分页插件来说,分页插件也不是万能的,它还是把查到的数据存储到内存中,可能会导致内存溢出风险,这点应该牢记在心。

<select id="getUsers" resultType="com.zhoutianyu.learnspringboot.mybatis.User">
select
<include refid="Base_Column_List"/>
from t_user
</select>

第十一节  SpringBoot使用Mybatis分页插件_局部变量_04

五、源码下载

        本章节项目源码:​​点我下载源代码​