第十一节 SpringBoot使用Mybatis分页插件
前置
在我参加工作的早期,分页查询都是通过手动编写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。
因此,我们编写如下的拦截器,拦截住前端的分页相关参数。
根据这些分页参数,构造出分页插件需要的PageRowBounds对象。
然后再把这个PageRowBounds对象存放到当前线程中,这里使用到了ThreadLocal技术。
ThreadLocal技术的原理就是,把要被存放的数据作为"线程局部变量"存放到线程中。
以后不管这个线程执行到哪里,都能够取到 "线程局部变量"。
关于ThreadLocal,我弄个示意图。更多关于ThreadLocal的知识,请参考多线程相关技术。
最后当请求执行完毕后,再用拦截器的后处理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;
四、测试
如下动图,实际的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>
五、源码下载
本章节项目源码:点我下载源代码