• MyBatis拦截器自定义分页插件实现


    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

  • 相关阅读:
    集合赋值及for循环删除符合条件的元素
    shiro系列12:rememberme(记住我)
    shiro系列11:缓存
    shiro系列10:会话管理
    shiro系列8:授权源码解析
    shiro系列7:拦截器
    shiro系列6:授权
    shiro系列5:Realm
    shiro系列4:认证源码解析
    shiro系列3:MD5盐值加密认证流程
  • 原文地址:https://www.cnblogs.com/SimpleWu/p/11692424.html
Copyright © 2020-2023  润新知