Springboot 手写 Mybatis分页插件

1、先看.yml配置文件中的写法配置:(boot-2.0.1)

mybatis:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.dalong
  configuration:
    lazy-loading-enabled: true
    aggressive-lazy-loading: false
    interceptors: {"com.dalong.interceptor.MyInterceptor"}
,里面还有一些别的配置,主要看interceptors就好了。

2、Mybatis拦截器类:

/**
 * FileName: MyInterceptor
 * Author:   dalong
 * Date:     2018/12/19/019 15:03
 * Description:
 * History:
 * <author>          <date>               <desc>
 * 作者姓名           修改时间                 描述
 */

package com.dalong.interceptor;

import com.dalong.page.PageParams;
import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.apache.ibatis.scripting.defaults.DefaultParameterHandler;
import org.apache.ibatis.session.Configuration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

@Component    //这个注解很重要。

//设置分页插件的签名
@Intercepts({
        @Signature(type = StatementHandler.class,
                method = "prepare",
                args={Connection.class,Integer.class})
})
public class MyInterceptor implements Interceptor{
    private final Logger logger= LoggerFactory.getLogger(MyInterceptor.class);

    private Integer defaultPage;//默认页码
    private Integer defaultPageSize;//默认每页条数
    private Boolean defaultUseFlag;//默认是否启用插件
    private Boolean defaultCheckFlag;//默认是否检测当前页码的正确性



    /**
     *   设置参数
     */
    @Override
    public void setProperties(Properties properties) {
        String strDefaultPage = properties.getProperty("default.page", "1");
        String strDefaultPageSize = properties.getProperty("default.pageSize", "50");
        String strDefaultUseFlag = properties.getProperty("default.useFlag", "false");
        String strDefaultCheckFlag = properties.getProperty("default.checkFlag", "false");
        this.defaultPage = Integer.parseInt(strDefaultPage);
        this.defaultPageSize = Integer.parseInt(strDefaultPageSize);
        this.defaultUseFlag = Boolean.parseBoolean(strDefaultUseFlag);
        this.defaultCheckFlag = Boolean.parseBoolean(strDefaultCheckFlag);
    }

    /**
     *   生成代理对象
     */
    @Override
    public Object plugin(Object o) {
        return Plugin.wrap(o,this);
    }
    /**
     * 插件运行的代码,它将代替原有的方法,要重写最重要的intercept方法
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = getUnProxyObject(invocation);
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        System.out.println("myinterceptor sql:----"+sql);
        logger.error(sql);
        //不是select语句
        if(!checkSelect(sql)){
            return invocation.proceed();
        }
        BoundSql boundSql = (BoundSql) metaStatementHandler.getValue("delegate.boundSql");
        Object parameterObject = boundSql.getParameterObject();
        PageParams pageParams = getPageParams(parameterObject);
        if(pageParams == null){  //没有分页参数,不启用插件
            return invocation.proceed();
        }
        //获取分页参数,获取不到使用默认值。
        Integer pageNum = pageParams.getPage() == null ? this.defaultPage : pageParams.getPage();
        Integer pageSize = pageParams.getPageSize() == null ? this.defaultPageSize : pageParams.getPageSize();
        Boolean useFlag = pageParams.getUseFlag() == null ? this.defaultUseFlag : pageParams.getUseFlag();
        Boolean checkFlag = pageParams.getCheckFlag() == null ? this.defaultCheckFlag : pageParams.getCheckFlag();
        if(!useFlag){
            return invocation.proceed();
        }
        int total = getTotal(invocation,metaStatementHandler,boundSql);
        //回填总数到分页参数里
        setTotalToPageParams(pageParams,total,pageSize);
        //检查当前页码的有效性
        checkPage(checkFlag,pageNum,pageParams.getTotalPage());
        return changeSql(invocation, metaStatementHandler, boundSql,pageNum,pageSize);
    }


    /**
     * 功能描述: 从代理对象分离出真实对象
     *
     *@param invocation
     * @return: 非代理StatementHandler 对象
     * @since: 1.0.0
     * @Author:dalong
     * @Date:
     */
    private StatementHandler getUnProxyObject(Invocation invocation){
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        MetaObject metaStatementHandler = SystemMetaObject.forObject(statementHandler);
        //分离代理对象链(由于目标类可能被多个拦截器拦截,从而形成多次代理,通过循环可以分离出最原始的目标类)
        Object object = null;
        while (metaStatementHandler.hasGetter("h")){
            object = metaStatementHandler.getValue("h");
        }
        if(object == null){
            return statementHandler;
        }
        return (StatementHandler) object;
    }

    /**
     * 功能描述: 判断是否是select语句。
     *
     *@param
     * @return:
     * @since: 1.0.0
     * @Author:dalong
     * @Date:
     */
    private boolean checkSelect(String sql){
        String trimSql = sql.trim();
        int idx = trimSql.toLowerCase().indexOf("select");
        return idx == 0;
    }

    /**
     * 功能描述: 获取分页参数,这里支持使用Map和@Param注解传递参数,或者POJO继承PageParams,三种方式。
     *
     *
     * @return:
     * @since: 1.0.0
     * @Author:dalong
     * @Date:
     */

    private PageParams getPageParams(Object parameterObject){
        if(parameterObject == null){
            return null;
        }
        PageParams pageParams = null;
        //支持MAP参数和MyBatis的@Param注解参数
        if(parameterObject instanceof Map){
            @SuppressWarnings("unchecked")
            Map<String,Object> paramMap = ( Map<String,Object>) parameterObject;
            Set<String> keySet = paramMap.keySet();
            Iterator<String> iterator = keySet.iterator();
            while (iterator.hasNext()){
                String key = iterator.next();
                Object value = paramMap.get(key);
                if(value instanceof PageParams){
                    return (PageParams) value;
                }
            }
        }else if(parameterObject instanceof PageParams){
            pageParams = (PageParams) parameterObject;
        }
        return pageParams;
    }

    /**
     * 功能描述:  获取总数
     * @return:
     * @since: 1.0.0
     * @Author:dalong
     * @Date:
     */

    private int getTotal(Invocation invocation,MetaObject metaStatementHandler,BoundSql boundSql) throws Throwable {
        //获取当前的MappedStatement
        MappedStatement mappedStatement = (MappedStatement) metaStatementHandler.getValue("delegate.mappedStatement");
        //配置对象
        Configuration cfg = mappedStatement.getConfiguration();
        //当前需要执行的sql
        String sql = (String)metaStatementHandler.getValue("delegate.boundSql.sql");
        //改写为统计总数的sql,此处用mysql库,其他的需要改写。
        String countSql = "select count(*) as total from ("+sql+") $_paging";
        //获取拦截的方法参数,是Connection对象。
        Connection conn = (Connection)invocation.getArgs()[0];
        PreparedStatement ps = null;
        int total = 0;
        try {
            //预编译统计总数sql
            ps = conn.prepareStatement(countSql);
            //构建统计总数boundsql
            BoundSql countBoundSql = new BoundSql(cfg,countSql,boundSql.getParameterMappings(),boundSql.getParameterObject());
            //构建MyBatis的ParameterHandler用来设置总数sql的参数
            ParameterHandler handler = new DefaultParameterHandler(mappedStatement,boundSql.getParameterObject(),countBoundSql);
            //设置总数sql参数
            handler.setParameters(ps);
            //执行查询
            ResultSet rs = ps.executeQuery();
            while(rs.next()){
                total = rs.getInt("total");
            }
        }finally {
            if(ps != null){
                ps.close();
            }
        }
        System.err.println("总条数:"+ total);
        return total;
    }

    /**
     * 功能描述:  回填总条数和总页数
     * @return:
     * @since: 1.0.0
     * @Author:dalong
     * @Date:
     */
    private void setTotalToPageParams(PageParams pageParams,int total,int pageSize){
        pageParams.setTotal(total);
        //计算总页数
        int totalPage = total % pageSize == 0 ? total/pageSize : total/pageSize + 1;
        pageParams.setTotalPage(totalPage);
    }

    /**
     * 功能描述:  判断当前页码是否大于最大页码
     * @return:
     * @since: 1.0.0
     * @Author:dalong
     * @Date:
     */
    private void checkPage(Boolean checkFlag,int pageNum,int totalPage) throws Throwable {
        if(checkFlag){
            //检查页码page是否合法
            if(pageNum > totalPage){
                throw new Exception("查询失败,查询页码【"+pageNum+"】 大于总页数 【"+totalPage+"】!!");
            }
        }
    }

    /**
     * 功能描述:  改写sql以满足分页的需求。
     * @return:
     * @since: 1.0.0
     * @Author:dalong
     * @Date:
     */

    private Object changeSql(Invocation invocation, MetaObject metaStatementHandler,BoundSql boundSql,
                             int pageNum,int pageSize) throws Exception {
        //获取当前要执行的sql
        String sql = (String) metaStatementHandler.getValue("delegate.boundSql.sql");
        //修改sql,使用的mysql,其他的需要按修改
        String newSql = "select * from ("+sql+") $_paging_table limit ?,?";
        //修改要执行的sql
        metaStatementHandler.setValue("delegate.boundSql.sql",newSql);
        //相当于调用StatementHandler的prepare方法,预编译了当前的sql并设置原有的参数,但是少了两个分页参数,它返回的是一个preparedStatement对象
        PreparedStatement ps = (PreparedStatement) invocation.proceed();
        //计算sql总参数个数
        int count = ps.getParameterMetaData().getParameterCount();
        //设置两个分页参数
        ps.setInt(count-1,(pageNum-1)*pageSize);
        ps.setInt(count,pageSize);
        return ps;
    }



}

 

   以上是自己照着书手抄的,只是结合了springboot的使用。

调的过程中,发现一直找不到prepare方法,就网上看了一下,MyBatis 3.4.0 之后,StatementHandler的prepare方法做了修改....具体可以看地址:https://blog.csdn.net/zsq520520/article/details/69666734

还有一个小问题,就是 setProperties 方法好像没有起作用,所以传入pageParams的时候所有初始需要的参数赋值哈,再进一步调调看。囧。。。。囧。。。。。。。囧。。。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值