MyBatis源码分析(四、续)MyBatis分页插件拦截器设计与实现

26 阅读3分钟

文章目录

系列文章索引

MyBatis源码分析(一)MyBatis整体架构分析
MyBatis源码分析(二)SqlSessionFactory的构建及配置文件读取过程
MyBatis源码分析(二、续)SqlSource创建流程,SQL如何解析?如何将#{id}变成?的
MyBatis源码分析(三)SqlSession的执行主流程
MyBatis源码分析(四)插件拦截器的原理及使用
MyBatis源码分析(四、续)MyBatis分页插件拦截器设计与实现
MyBatis源码分析(五)一级缓存与二级缓存的原理
MyBatis源码分析(六)MetaObject工具类的使用与源码分析
MyBatis源码分析(七)MyBatis与Spring的整合原理与源码分析
深入理解JDK动态代理原理,使用javassist动手写一个动态代理框架

一、分页插件功能特性

1.易用性:不需要额外配置,参数中带上Page即可,Page尽可能简单。

2.不对使用场景作假设:不限制用户使用,如接口调用,还是回话调用,又或是对Executor以及StatementHandler的选择等,不能影响缓存业务。

3.友好性:当不符合分页情况下,作出友好的用户提示。如在修改操作中传入分页参数,或用户本身已在查询语句中自带分页语句,这种情况应作出提示(抛异常)。

二、设计一个基本的分页插件

public class Page {

    private int total; // 总行数
    private int size; // 每页大小
    private int index; // 页码,从1开始

    public int getTotal() {
        return total;
    }

    public void setTotal(int total) {
        this.total = total;
    }

    public int getSize() {
        return size;
    }

    public void setSize(int size) {
        this.size = size;
    }

    public int getIndex() {
        return index;
    }

    public void setIndex(int index) {
        this.index = index;
    }

    // offset 0 limit 50
    public int getOffset(){
        return size * (index -1);
    }
}

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.SystemMetaObject;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.Properties;

/**
 * 分页拦截器
 */
@Intercepts(
        @Signature(
            type = StatementHandler.class,
            method = "prepare",
            args = {Connection.class, Integer.class}
        )
)
public class PageInterceptor implements Interceptor {

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        // 1.检测当前是否满足分页条件
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        // sql包(sql、参数、参数映射)
        BoundSql boundSql = statementHandler.getBoundSql();
        // 参数(单个参数时就是参数类型,多个参数的话会转成map)
        Object parameterObject = boundSql.getParameterObject();

        // 查出是否有Page
        Page page = null;
        if(parameterObject instanceof Page){
            // 参数就是Page
            page = (Page) parameterObject;
        } else if(parameterObject instanceof Map) {
            // 多个参数的情况下需要遍历出Page
            page = (Page)((Map) parameterObject).values().stream().filter(v -> v instanceof Page).findFirst().orElse(null);
        }

        if(page == null){
            return invocation.proceed();
        }

        // 2.设置总行数
        page.setTotal(selectCount(invocation));

        // 3.修改原有SQL
        String newSql = String.format("%s limit %s offset %s", boundSql.getSql(), page.getSize(), page.getOffset());
        SystemMetaObject.forObject(boundSql).setValue("sql", newSql);
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        // 配置分页插件时的配置
    }

    /**
     * 查询总数
     */
    private int selectCount(Invocation invocation) throws SQLException {
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        // count语句
        String countSql = String.format("select count(*) from (%s) as _page", boundSql.getSql());
        Connection connection = (Connection) invocation.getArgs()[0];
        ParameterHandler parameterHandler = statementHandler.getParameterHandler();
        return executeCount(parameterHandler, connection, countSql);


    }

    private int executeCount(ParameterHandler parameterHandler, Connection connection, String countSql) {
        try (PreparedStatement stmt = prepareStatement(parameterHandler, connection, countSql);
             ResultSet rs = stmt.executeQuery()) {
            return rs.next() ? unboxing(rs.getInt(1), 0) : 0;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    private PreparedStatement prepareStatement(ParameterHandler parameterHandler, Connection connection, String countSql) throws SQLException {
        PreparedStatement prepareStatement = connection.prepareStatement(countSql);
        parameterHandler.setParameters(prepareStatement);
        return prepareStatement;
    }

    private int unboxing(Integer value, int defaultValue) {
        return value != null ? value : defaultValue;
    }
}

1、缺陷分析

对二级缓存不友好,并且如果XML中带有collection标签的话,会造成数据行数紊乱。

三、分享一个分页插件:

pageHelper

写在后面

如果本文对你有帮助,请点赞收藏关注一下吧 ~
在这里插入图片描述