Mybatis 分页插件 进阶使用

我们的创建顺序及简述
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);
    }
以上配置完后就可以直接使用啦。有具体的需求需要更改的可以再进心更改。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,772评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,458评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,610评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,640评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,657评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,590评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,962评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,631评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,870评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,611评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,704评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,386评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,969评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,944评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,179评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,742评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,440评论 2 342

推荐阅读更多精彩内容