第十一节 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>

五、源码下载

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

        目录贴:跟着大宇学SpringBoot-------目录帖

 

posted @ 2022-07-17 12:14  小大宇  阅读(899)  评论(0编辑  收藏  举报