MyBaits是一个开源的优秀的持久层框架,SQL语句与代码分离,面向配置的编程,良好支持复杂数据映射,动态SQL;MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架。MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
通常使用MyBatis时使用以下几种形式进行分页:

  1. 逻辑分页:RowBounds
  2. 物理分页: 在SQL里面使用LIMIT或者使用第三方插件(PageHelper等)

    环境及介绍

    导入核心依赖:

 <!-- SpringBoot MyBatis starter -->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.2.2</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
</dependency>

搭建环境步骤可以参考:SpringBoot企业中常用starter
本次主要实现一个接口org.apache.ibatis.plugin.Interceptor,在接口中有3个方法为:

Object intercept(Invocation invocation) throws Throwable;

Object plugin(Object target);

void setProperties(Properties properties);
  1. intercept 方法是主要拦截执行方法。
  2. plugin 方法是决定当前对象是否需要生成代理对象。
  3. setProperties 设置运行时mybatis核心配置参数方法。
    Mybatis的拦截器实现机制,使用的是JDKInvocationHandler,当我们调用ParameterHandler,ResultSetHandler,StatementHandler,Executor的对象的时候,实际上使用的是Plugin这个代理类的对象,这个类实现了InvocationHandler接口。
    当我们调用ParameterHandler,ResultSetHandler,StatementHandler,Executor的对象的时候,
    实际上使用的是Plugin这个代理类的对象,这个类实现了InvocationHandler接口。

    自定义分页实现

    创建Pager实体对象,用来进行分页,实体内容如下:

@Data
@ToString
public class Pager {
    /*当前页*/
    private int page;
    /*每页大小*/
    private int size;
    /*总记录*/
    private long total;
    /*总页数*/
    private int totalPage;
    /*自定义分页sql*/
    private String customSQL;
    /*分页执行时长*/
    private long executeTime;
}

这里customSQL变量为自定义分页SQL,很多时候以为sql过于复杂关联了N张表,获取了N个字段会造成查询时间过于缓慢,加入这个字段主要是为了在一些复杂的SQL中不暴力使用默认的分页数量统计,可以自己根据SQL去除不需要的字段,已经不需要的表连接后的SQL来执行分页数量的统计。
定义分页处理接口PagerHandler,内容如下:

public interface PagerHandler {
    /**
     * 获取sql执行参数
     * @param boundSql
     * @return
     */
    public Pager getPager(BoundSql boundSql);

    /**
     * 执行分页
     *
     * @param pager
     * @param boundSql
     * @param connection
     * @param metaObject
     * @return
     * @throws SQLException
     */
    public Pager executer(Pager pager, BoundSql boundSql, Connection connection, MetaObject metaObject) throws SQLException;
}

创建MyBats拦截器实现分页PagerMyBatisInterceptor,该类实现接口org.apache.ibatis.plugin.Interceptor和我们自己定义的PagerHandler,重写接口中方法。
我们还需要告诉MyBatis具体在什么地点进行拦截,使用@Intercepts来标注:

@Intercepts(value = {
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})

主要拦截StatementHandlerprepare编译参数方法该方法需要传入参数类型为Connection.class, Integer.class,在MyBatis 3.4.1版本下参数只有一个Connection
在类中定义一些常量:

private final Logger log = LoggerFactory.getLogger(PagerMyBatisInterceptor.class);

private final int CONNECTION_INDEX = 0; //连接参数索引

private final DefaultReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();//默认反射工厂

private final String DELEGATE_MAPPED_STATEMENT = "delegate.mappedStatement";//反射值获取路径

private final String DELEGATE_PARAMETER_HANDLER = "delegate.parameterHandler";//反射值获取路径

具体拦截器内容:

@Component
@Intercepts(value = {
        @Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class PagerMyBatisInterceptor implements PagerHandler, Interceptor {

    private final Logger log = LoggerFactory.getLogger(PagerMyBatisInterceptor.class);

    private final int CONNECTION_INDEX = 0;

    private final DefaultReflectorFactory DEFAULT_REFLECTOR_FACTORY = new DefaultReflectorFactory();

    private final String DELEGATE_MAPPED_STATEMENT = "delegate.mappedStatement";

    private final String DELEGATE_PARAMETER_HANDLER = "delegate.parameterHandler";

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        //数据库连接
        Connection connection = (Connection) args[CONNECTION_INDEX];
        //负责处理Mybatis与JDBC之间Statement的交互
        StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
        //MetaObject是Mybatis提供的一个用于方便、优雅访问对象属性的对象,通过它可以简化代码、不需要try/catch各种reflect异常,同时它支持对JavaBean、Collection、Map三种类型对象的操作。
        MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY,
                SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY, DEFAULT_REFLECTOR_FACTORY);
        //MappedStatement表示的是XML中的一个SQL
        MappedStatement mappedStatement = (MappedStatement) metaObject.getValue(DELEGATE_MAPPED_STATEMENT);
        //SqlCommandType代表SQL类型
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        //BoundSql则是其保存Sql语句的对象
        BoundSql boundSql = statementHandler.getBoundSql();
        //分页对象
        Pager pager = getPager(boundSql);
        if (sqlCommandType.compareTo(SqlCommandType.SELECT) == 0 && pager != null) {
            executer(pager, boundSql, connection, metaObject);
            //执行查询
            int left = (pager.getPage() - 1) * pager.getSize();
            int right = pager.getSize();
            String rewriteSql = boundSql.getSql() + " LIMIT " + left + "," + right;
            metaObject.setValue("boundSql.sql", rewriteSql);
        }
        long startTime = System.currentTimeMillis();
        Object proceed = invocation.proceed();
        long endTime = System.currentTimeMillis();
        log.info("SQL TYPE [{}] , SQL EXECUTE TIME [{}] SQL:\n{}", sqlCommandType, startTime - endTime, boundSql.getSql().toUpperCase());
        return proceed;
    }

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

    @Override
    public void setProperties(Properties properties) {
        //TODO 设置mybatis参数
    }

    @Override
    public Pager getPager(BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        if (parameterObject instanceof Pager) {
            return (Pager) parameterObject;
        } else if (parameterObject instanceof Map) {
            Map<String, Object> paramMap = (Map<String, Object>) parameterObject;
            Iterator<String> keys = paramMap.keySet().iterator();
            while (keys.hasNext()) {
                String key = keys.next();
                Object obj = paramMap.get(key);
                if (obj instanceof Pager) {
                    return (Pager) obj;
                }
            }

        }
        return null;
    }

    @Override
    public Pager executer(Pager pager, BoundSql boundSql, Connection connection, MetaObject metaObject) throws SQLException {
        if (pager.getPage() == 0) {
            pager.setPage(0);
        }
        if (pager.getSize() == 0) {
            pager.setSize(0);
        }
        if (pager.getCustomSQL() == null) {
            //如果自己没有定义分页SQL,那么使用默认暴力分页
            pager.setCustomSQL("SELECT COUNT(1) FROM (" + boundSql.getSql() + " ) tmp_table");
        }
        // 预编译
        PreparedStatement prepareStatement = connection.prepareStatement(pager.getCustomSQL());
        // 预编译执行
        ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue(DELEGATE_PARAMETER_HANDLER);
        parameterHandler.setParameters(prepareStatement); // 给sql语句设置参数
        long startTime = System.currentTimeMillis();
        ResultSet resultSet = prepareStatement.executeQuery();
        long endTime = System.currentTimeMillis();
        log.info("sql execute time {} sql:\n{}", startTime - endTime, pager.getCustomSQL().toUpperCase());
        if (resultSet.next()) {
            long total = (long) resultSet.getObject(1);// 总记录数量
            int totalPageNum = (int) ((total + pager.getSize() - 1) / pager.getSize());
            pager.setTotal(total);
            pager.setTotalPage(totalPageNum);
            pager.setExecuteTime(startTime - endTime);
        }
        return pager;
    }
}

通过方法getPager获取到pager对象,如果是Map参数那么就优先第一个通过引用的传递在BoundSql对象中获取到,然后执行分页获取里面的值进行计算,通过引用对象返回总记录数,总页数等。
在SpringBoot中如果需要使拦截器生效只需要在类型使用@Component将该类交给Spring IOC管理即可,至于拦截器顺序如:
有拦截器 PagerMyBatisInterceptorOneInterceptor ,想要分页拦截器作为第二个拦截器只需要在类上标注@ConditionalOnBean(OneInterceptor)即可,在第一个拦截器实例化后再实例化第二个拦截器.

Pager使用方式

 List<Map<String, Object>> selectUser(Pager pager);
 List<Map<String, Object>> selectUser(Map<String,Object> paramMap);

创建一个Pager对象传入即可。
本文源码地址:https://github.com/450255266/open-doubi/tree/master/spring-boot/custom-mybatis-pager

MyBatis拦截器自定义分页插件实现的更多相关文章

  1. Mybatis拦截器实现分页

    本文介绍使用Mybatis拦截器,实现分页:并且在dao层,直接返回自定义的分页对象. 最终dao层结果: public interface ModelMapper { Page<Model&g ...

  2. mybatis拦截器实现分页功能的示例讲解

    import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import jav ...

  3. Mybatis拦截器介绍及分页插件

    1.1    目录 1.1 目录 1.2 前言 1.3 Interceptor接口 1.4 注册拦截器 1.5 Mybatis可拦截的方法 1.6 利用拦截器进行分页 1.2     前言 拦截器的一 ...

  4. Mybatis拦截器(插件实现原理)

    在mybatis的mybatis.cfg.xml中插入: <plugins> <plugin interceptor="cn.sxt.util.PageIntercepto ...

  5. mybaits拦截器+自定义注解

    实现目的:为了存储了公共字典表主键的其他表在查询的时候不用关联查询(所以拦截位置位于mybaits语句查询得出结果集后) 项目环境 :springboot+mybaits 实现步骤:自定义注解——自定 ...

  6. spring boot 实现mybatis拦截器

    spring boot 实现mybatis拦截器 项目是个报表系统,服务端是简单的Java web架构,直接在请求参数里面加了个query id参数,就是mybatis mapper的query id ...

  7. mybatis拦截器使用

    目录 mybatis 拦截器接口Interceptor spring boot + mybatis整合 创建自己的拦截器MyInterceptor @Intercepts注解 mybatis拦截器入门 ...

  8. Mybatis拦截器执行过程解析

    上一篇文章 Mybatis拦截器之数据加密解密 介绍了 Mybatis 拦截器的简单使用,这篇文章将透彻的分析 Mybatis 是怎样发现拦截器以及调用拦截器的 intercept 方法的 小伙伴先按 ...

  9. Mybatis拦截器实现原理深度分析

    1.拦截器简介 拦截器可以说使我们平时开发经常用到的技术了,Spring AOP.Mybatis自定义插件原理都是基于拦截器实现的,而拦截器又是以动态代理为基础实现的,每个框架对拦截器的实现不完全相同 ...

随机推荐

  1. maven打包插件maven-assembly-plugin

    1.POM文件添加jar包生成插件 <plugin> <groupId>org.apache.maven.plugins</groupId> <artifac ...

  2. AWGN

    高斯白噪声的功率谱密度服从均匀分布,幅度分布服从高斯分布: 白噪声是指它的二阶矩不相关,一阶矩为常数,是指先后信号在时间上的相关性: 高斯白噪声在任意两个不同时刻上的随机变量之间,不仅是互不相关的,而 ...

  3. Winform中跨窗体设置Zedgraph的属性并刷新曲线图

    场景 在使用ZedGraph时,经常有图形选项功能,设置曲线图相关属性后, 点击保存会设置另一个窗体的属性并刷新图. 效果 实现 在设置图形的选项的类中,声明委托和事件 //委托的定义 public ...

  4. Nginx限流

    文章原创于公众号:程序猿周先森.本平台不定时更新,喜欢我的文章,欢迎关注我的微信公众号. 在当今流量徒增的互联网时代,很多业务场景都会涉及到高并发.这个时候接口进行限流是非常有必要的,而限流是Ngin ...

  5. 第一次登陆jenkins页面空白解决方案

    之前搭建了几次jenkins环境都没问题,最近换了工作,再次搭建jenkins用的是docker部署: https://www.cnblogs.com/yy-cola/p/10457484.html ...

  6. 7.Sentinel源码分析—Sentinel是怎么和控制台通信的?

    这里会介绍: Sentinel会使用多线程的方式实现一个类Reactor的IO模型 Sentinel会使用心跳检测来观察控制台是否正常 Sentinel源码解析系列: 1.Sentinel源码分析-F ...

  7. JAVASE知识点总结(四)

    第十七章:内部类和枚举 一.内部类 1.什么是内部类? 2.为什么要有内部类? 3.内部类的种类: 5.实例内部类 6.创建实例内部类 7.实例内部类注意点 8.访问变量原则: 4 9.静态内部类: ...

  8. Spring Boot2 系列教程(九)Spring Boot 整合 Thymeleaf

    虽然现在慢慢在流行前后端分离开发,但是据松哥所了解到的,还是有一些公司在做前后端不分的开发,而在前后端不分的开发中,我们就会需要后端页面模板(实际上,即使前后端分离,也会在一些场景下需要使用页面模板, ...

  9. 基于Docker搭建大数据集群(二)基础组件配置

    主要内容 jdk环境搭建 scala环境搭建 zookeeper部署 mysql部署 前提 docker容器之间能免密钥登录 yum源更换为阿里源 安装包 微云分享 | tar包目录下 JDK 1.8 ...

  10. Java源码跟踪阅读技巧

    转:https://www.jianshu.com/p/ab865109070c 本文基于Eclipse IDE 1.Quick Type Hierarchy 快速查看类继承体系. 快捷键:Ctrl ...