美文网首页
Mybatis 分页插件 进阶使用

Mybatis 分页插件 进阶使用

作者: kacen | 来源:发表于2022-07-14 10:20 被阅读0次

    我们的创建顺序及简述
    1.创建分页实体对
    2.创建分页工具类对象

    1. 创建分页插件 PagePlugin.java 这个类主要是分页逻辑的具体实现
    2. 创建PageInterceptor分页拦截器类 PageInterceptor.java 这个类主要是在客户端传递分页信息时,进行拦截的作用。
    3. 创建 基于springmvc响应处理器 - 处理返回Result对象统一设置分页信息 PageResponseBodyAdvice
    4. 创建Mybatis相关的工具类 MybatisUtils.java

    首先保证有以下依赖

            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
            </dependency>
    

    1. 创建分页实体对象

    @Data
    public class Page {
        /**
         * 当前页
         */
        private Integer pageNum;
    
        /**
         * 每页显示的条数
         */
        private Integer pageSize;
    
        /**
         * 总条数
         */
        private Integer pageCount;
    
        /**
         * 总页码
         */
        private Integer pageTotal;
    }
    

    2..创建分页工具类对象

    public class ExamplePage {
    
        public static ThreadLocal<Page> pageThreadLocal = new ThreadLocal<>();
    
        /**
         * 设置分页信息
         */
        public static void setPage(Integer pageNum, Integer pageSize) {
            Page page = new Page();
            page.setPageNum(pageNum);
            page.setPageSize(pageSize);
            pageThreadLocal.set(page);
        }
    
        /**
         * 获取分页信息
         */
        public static Page getPage() {
            return  pageThreadLocal.get();
        }
    
        /**
         * 清除TreadLocal
         */
        public static void clear() {
            pageThreadLocal.remove();
        }
    }
    

    3. 创建分页插件 PagePlugin.java

    这里我的话是实现了Interceptor来自定义mybatis分页插件。
    因为我们需要通过Interceptor来重写相关方法。
    @Intercepts的话是指定需要拦截的地方, 类StatementHandler, 方法 prepare, 参数args = {Connection.class, Integer.class}。

    当我们在设置页码的时候,很多人会这样写。
    我们会先用 pageTotal=PageCount/pageSize 这个来算总页码
    pageNum < 1,我们会直接设置为1,
    当pageNum > pageTotal总页码的时候,我们会直接设置会总页码。
    但是这样写的话会有一个问题,就是PageCount和pageSize都为0的时候那就很尴尬了。
    这样子的话会有一个问题
    逻辑会先走pageTotal=PageCount/pageSize,并设置PageNum为1,
    然后再走pageNum > pageTotal,那这里的pageNum是=1的,而pageTotal为0,最后pageNum又会设置成0.
    然后往下走的话会走到我们都会用的分页算法
    这里如果再往下走会是0-1=-1,然后报错。
    sql += " limit " + (page.getPageNum() - 1 ) * page.getPageSize() + "," + page.getPageSize();

    详细代码
    这里是具体的步骤中的问题代码

    if (page.getPageNum() > page.getPageTotal()) {
                page.setPageNum(page.getPageTotal());
            }
    
            // 开始进行分页,这里用的是拼接
            // 策略模式 - 根据不同的数据库,选择使用不同的sql分页模式
            // limit 后面需要 (加页码-1)*页面大小, 页面大小(这是算法)
            sql += " limit " + (page.getPageNum() - 1 ) * page.getPageSize() + "," + page.getPageSize();
            log.debug("[page - plugin] 分页sql - {}", sql)
    

    这个是PagePlugin整个类哈,还有问题代码相关的修改

    @Intercepts(
            @Signature(
                    type = StatementHandler.class,
                    method = "prepare",
                    args = {Connection.class, Integer.class}
            )
    )
    @Slf4j
    public class PagePlugin implements Interceptor {
    
        /**
         * 拦截Mybatis PrepareStatement
         * @param invocation
         * @return
         * @throws Throwable
         */
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            // 获取StatementHandler
            // 这里获取到的将是RoutingStatement
            StatementHandler statementHandler = (StatementHandler) MybatisUtils.getProxyObject(invocation.getTarget());
            System.out.println("当前stament对象:"+ statementHandler.getClass().getSimpleName());
            //获取boundSql,里面含有sql语句
            //因为在进行sql语句执行前会通过boundSql进行绑定赋值,所以在这里先获取,以便在拦截并处理后做相应的替换动作
            BoundSql boundSql = statementHandler.getBoundSql();
    
            // 获取拦截的sql语句
            String sql = statementHandler.getBoundSql().getSql().toLowerCase().replaceAll("\n", "");
    
            // 判断当前是否为查询的sql,是才可能分页,不是的话就不分页
            if (!sql.startsWith("select")){
                //非查询语句
                return invocation.proceed();
            }
    
            // 根据当前请求的上下文,获取Page分页对象,如果能获取到,说明需要分页,如果不需要获取,说明无须分页
            // TreadLocal 主要做线程隔离
            Page page = ExamplePage.getPage();
            if (page == null) {
                // 未获取到分页对象
                return invocation.proceed();
            }
            // 开始分页
            log.debug("[page - plugin] 开始分页 - {}", sql);
            // 计算出总条数
            Integer count = getCount(sql, invocation, statementHandler);
            // 进行相关的参数设置
            // 默认显示10条
            if (page.getPageSize() == null) {
                page.setPageSize(10);
            }
            // 设置总条数
            page.setPageCount(count);
            // 总条数%设置的页数大小,如果可以整除证明只显示一页,如果!=0则需要添加页数
            page.setPageTotal(page.getPageCount() % page.getPageSize() == 0 ?
                    page.getPageCount() / page.getPageSize() :
                    page.getPageCount() / page.getPageSize() + 1
                    );
            // 判断页码的临界点,如果<1的话就直接设置为1,大于页码数的话就直接设置为页码数
            if (page.getPageNum() < 1) {
                page.setPageNum(1);
            }
            if (page.getPageNum() > page.getPageTotal() && page.getPageTotal() > 0) {
                page.setPageNum(page.getPageTotal());
            }
    
            // 开始进行分页,这里用的是拼接
            // 策略模式 - 根据不同的数据库,选择使用不同的sql分页模式
            // limit 后面需要 (加页码-1)*页面大小, 页面大小(这是算法)
            sql += " limit " + (page.getPageNum() - 1 ) * page.getPageSize() + "," + page.getPageSize();
            log.debug("[page - plugin] 分页sql - {}", sql);
    
            // 开始执行分页sql
            // 将修改后的sql替换原来的sql,然后放行
            MetaObject metaObject = SystemMetaObject.forObject(boundSql);
            metaObject.setValue("sql", sql);
            log.debug("[page - plugin] 分页完成!");
            //放行,因为已经修改好了,Mybatis会执行修改后的sql
            return invocation.proceed();
        }
    
        /**
         * 计算查询到总条数
         * @return 返回总条数
         */
        private  Integer getCount(String sql, Invocation invocation, StatementHandler statementHandler) throws SQLException {
    
            int count = 0;
            // 获取相关参数
            Connection connection = (Connection) invocation.getArgs()[0];
            // *后面必须有 空格
            String countSql = "select count(*) as count " + sql.substring(sql.indexOf("from"));
            log.debug("[page - plugin] 生成记录总条数的sql语句 - {}", countSql);
            // 执行当前的sql语句
            ResultSet resultSet = null;
            PreparedStatement preparedStatement = null;
            try{
                preparedStatement = connection.prepareStatement(countSql);
                // 设置sql相关参数, Mybatis自带的StatamentHandler中页有parametersize这个方法来将参数进行设置,
                // 所以只要直接调用其方法既可跟平时写的sql一样将参数传入
                statementHandler.parameterize(preparedStatement);
                // 执行sql
                resultSet = preparedStatement.executeQuery();
    
                if (resultSet.next()) {
                    // 直接获取count结果
                    count = resultSet.getInt("count");
                    log.debug("[page - plugin] 获取查询的记录总条数 {}", count);
                    return count;
                }
            }catch (SQLException e) {
                throw new RuntimeException(e);
            } finally{
                if (resultSet != null) {
                    resultSet.close();
                }
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
            }
            return count;
        }
    }
    

    4. 创建PageInterceptor分页拦截器类 PageInterceptor.java

    @Component
    @Slf4j
    public class PageInterceptor extends HandlerInterceptorAdapter {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            String pageNumStr = request.getParameter("pageNum");
            String pageSizeStr = request.getParameter("pageSize");
            log.debug("[page interceptor] 分页拦截器设置pageNum:{},设置pageSize{}",pageNumStr,pageSizeStr);
            if (pageNumStr != null && pageSizeStr != null) {
                try {
                    int pageNum = Integer.parseInt(pageNumStr);
                    int pageSize = Integer.parseInt(pageSizeStr);
                    PbShopPage.setPage(pageNum,pageSize);
                } catch (Throwable e) {
                    return true;
                }
            }
            return true;
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            //清空ThreadLocal
            log.debug("[page interceptor] 分页拦截器后置处理,清除ThreadLocal");
            PbShopPage.clear();
        }
    }
    
    

    5. 创建 基于springmvc响应处理器 - 处理返回Result对象统一设置分页信息 PageResponseBodyAdvice

    @Slf4j
    @RestControllerAdvice
    public class PageResponseBodyAdvice implements ResponseBodyAdvice<Result> {
    
        /**
         * 判断什么条件出发beforeBodyWrite方法
         * @param returnType the return type
         * @param converterType the selected converter type
         * @return
         */
        @Override
        public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
            // 判断类型是否是Result类型的
            return returnType.getMethod().getReturnType() == Result.class;
        }
    
        @Override
        public Result beforeBodyWrite(Result body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
            //获取Threadlocal中的page对象
            Page page = PbShopPage.getPage();
            if (page == null) {
                return body;
            }
            log.debug("[page responseAdvice] 分页返回值处理器生效: {}",page);
            //将分页信息放到Result实体类中一并返回给客户端
            body.setPage(page);
            return body;
        }
    }
    

    6. 创建Mybatis相关的工具类 MybatisUtils.java

    /**
         * 直接获取到代理的内置对象
         * @param target
         * @return
         */
        public static Object getProxyObject(Object target) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            while (metaObject.hasGetter("h")) {
                // 说明当前是一个代理对象
                // 获取代理对象中持有的对象
                target = metaObject.getValue("h.target");
                metaObject = SystemMetaObject.forObject(target);
            }
            return target;
        }
    
        /**
         * 返回一个sql语句中 主标的from 的index的位置
         * @param beginIndex 起始from位置
         * @param sql 传入主sql语句
         * @return
         */
        public static Integer getMainFrom(int beginIndex, String sql) {
            if (sql == null) {
                return -1;
            }
            //从index开始处往后查找,获取from关键词的下表
            int fromIndex = sql.indexOf(" from ", beginIndex);
            if (fromIndex == -1) {
                return -1;
            }
    
            String sqlSub = sql.substring(0, fromIndex);
            System.out.println(sqlSub);
            int count = 0;
            //转换成char数组
            char[] chars = sqlSub.toCharArray();
            for (int i = 0; i < chars.length; i++) {
                char c = chars[i];
                if (c == '(') {
                    count++;
                }
                if (c == ')') {
                    count--;
                }
            }
    
            //判断计数器
            if (count == 0) {
                return fromIndex;
            }else {
                return getMainFrom(fromIndex + 6, sql);
            }
        }
    
        public static void main(String[] args) {
            String sql = "select asdf,asdf,(select a,(select as from sjs),(select 1,3 from b),a from A) from Order where id = (select id from t)";
        System.out.println(getMainFrom(0, sql));
            String s = "select count(*) as count" + sql.substring(getMainFrom(0, sql));
            System.out.println(s);
        }
    
    以上配置完后就可以直接使用啦。有具体的需求需要更改的可以再进心更改。

    相关文章

      网友评论

          本文标题:Mybatis 分页插件 进阶使用

          本文链接:https://www.haomeiwen.com/subject/bpjtirtx.html