zoukankan      html  css  js  c++  java
  • MyBatis 示例-插件

    简介

    利用 MyBatis Plugin 插件技术实现分页功能。

    分页插件实现思路如下:

    • 业务代码在 ThreadLocal 中保存分页信息;
    • MyBatis Interceptor 拦截查询请求,获取分页信息,实现分页操作,封装分页列表数据返回;

    测试类:com.yjw.demo.PageTest

    插件开发过程

    确定需要拦截的签名

    MyBatis 插件可以拦截四大对象中的任意一个,从 Plugin 源码中可以看到它需要注册签名才能够运行插件,签名需要确定一些要素。

    确定需要拦截的对象

    • Executor 是执行 SQL 的全过程,包括组装参数,组装结果集返回和执行 SQL 过程,都可以拦截。
    • StatementHandler 是执行 SQL 的过程,我们可以重写执行 SQL 的过程。
    • ParameterHandler 是拦截执行 SQL 的参数组装,我们可以重写组装参数规则。
    • ResultSetHandler 用于拦截执行结果的组装,我们可以重写组装结果的规则。

    拦截方法和参数

    当确定了需要拦截什么对象,接下来就要确定需要拦截什么方法和方法的参数。比如分页插件需要拦截 Executor 的 query 方法,我们先看看 Executor 接口的定义,代码清单如下:

    public interface Executor {
     
      ResultHandler NO_RESULT_HANDLER = null;
     
      int update(MappedStatement ms, Object parameter) throws SQLException;
     
      <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
     
      <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
     
      <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException;
     
      List<BatchResult> flushStatements() throws SQLException;
     
      void commit(boolean required) throws SQLException;
     
      void rollback(boolean required) throws SQLException;
     
      CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
     
      boolean isCached(MappedStatement ms, CacheKey key);
     
      void clearLocalCache();
     
      void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
     
      Transaction getTransaction();
     
      void close(boolean forceRollback);
     
      boolean isClosed();
     
      void setExecutorWrapper(Executor executor);
     
    }

    以上的任何方法都可以拦截,从接口定义而言,query 方法有两个,我们可以按照代码清单来定义签名。

    @Intercepts({
            @Signature(type = Executor.class, method = "query",
                    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
            @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
                    ResultHandler.class, CacheKey.class, BoundSql.class})})

    其中,@Intercepts 说明它是一个拦截器。@Signature 是注册拦截器签名的地方,type 是四大对象中的一个,method 是需要拦截的方法,args 是方法的参数。

    插件接口定义

    在 MyBatis 中开发插件,需要实现 Interceptor 接口,接口的定义如下:

    public interface Interceptor {
     
      Object intercept(Invocation invocation) throws Throwable;
     
      Object plugin(Object target);
     
      void setProperties(Properties properties);
     
    }
    • intercept 方法:它将直接覆盖你所拦截对象原有的方法,因此它是插件的核心方法。通过 invocation 参数可以反射调度原来对象的方法。
    • plugin 方法:target 是被拦截对象,它的作用是给被拦截对象生成一个代理对象,并返回它。为了方便 MyBatis 使用 org.apache.ibatis.plugin.Plugin 中的 wrap 静态方法提供生成代理对象。
    • setProperties 方法:允许在 plugin 元素中配置所需参数,方法在插件初始化的时候就被调用了一次,然后把插件对象存入到配置中,以便后面再取出。

    实现类

    根据分页插件的实现思路,定义了三个类。

    Page 类

    Page 类继承了 ArrayList 类,用来封装分页信息和列表数据。

    /**
     * 分页返回对象
     * 
     * @author yinjianwei
     * @date 2018/11/05
     */
    public class Page<E> extends ArrayList<E> {
    
        private static final long serialVersionUID = 1L;
    
        /**
         * 页码,从1开始
         */
        private int pageNum;
        /**
         * 页面大小
         */
        private int pageSize;
        /**
         * 起始行
         */
        private int startRow;
        /**
         * 末行
         */
        private int endRow;
        /**
         * 总数
         */
        private long total;
        /**
         * 总页数
         */
        private int pages;
    
        public int getPageNum() {
            return pageNum;
        }
    
        public void setPageNum(int pageNum) {
            this.pageNum = pageNum;
        }
    
        public int getPageSize() {
            return pageSize;
        }
    
        public void setPageSize(int pageSize) {
            this.pageSize = pageSize;
        }
    
        public int getStartRow() {
            return startRow;
        }
    
        public void setStartRow(int startRow) {
            this.startRow = startRow;
        }
    
        public int getEndRow() {
            return endRow;
        }
    
        public void setEndRow(int endRow) {
            this.endRow = endRow;
        }
    
        public long getTotal() {
            return total;
        }
    
        public void setTotal(long total) {
            this.total = total;
            this.pages = (int)(total / pageSize + (total % pageSize == 0 ? 0 : 1));
            if (pageNum > pages) {
                pageNum = pages;
            }
            this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0;
            this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0);
        }
    
        public int getPages() {
            return pages;
        }
    
        public void setPages(int pages) {
            this.pages = pages;
        }
    
        /**
         * 返回当前对象
         * 
         * @return
         */
        public List<E> getResult() {
            return this;
        }
    
    }

    PageHelper 类

    PageHelper 类是分页的帮助类,主要利用 ThreadLocal 线程变量存储分页信息。代码清单如下:

    /**
     * 分页帮助类
     * 
     * @author yinjianwei
     * @date 2018/11/05
     */
    @SuppressWarnings("rawtypes")
    public class PageHelper {
    
        private static final ThreadLocal<Page> PAGE_THREADLOCAT = new ThreadLocal<Page>();
    
        /**
         * 设置线程局部变量分页信息
         * 
         * @param page
         */
        public static void setPageThreadLocal(Page page) {
            PAGE_THREADLOCAT.set(page);
        }
    
        /**
         * 获取线程局部变量分页信息
         * 
         * @return
         */
        public static Page getPageThreadLocal() {
            return PAGE_THREADLOCAT.get();
        }
    
        /**
         * 清空线程局部变量分页信息
         */
        public static void pageThreadLocalClear() {
            PAGE_THREADLOCAT.remove();
        }
    
        /**
         * 设置分页参数
         * 
         * @param pageNum
         * @param pageSize
         */
        public static void startPage(Integer pageNum, Integer pageSize) {
            Page page = new Page();
            page.setPageNum(pageNum);
            page.setPageSize(pageSize);
            setPageThreadLocal(page);
        }
    
    }

    PageInterceptor 类

    PageInterceptor 类实现了 Interceptor 接口,是分页插件的核心类。代码清单如下:

    /**
     * 分页拦截器
     * 
     * @author yinjianwei
     * @date 2018/11/05
     */
    @Intercepts({
        @Signature(type = Executor.class, method = "query",
            args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
            ResultHandler.class, CacheKey.class, BoundSql.class})})
    public class PageInterceptor implements Interceptor {
    
        private Field additionalParametersField;
    
        @SuppressWarnings({"rawtypes", "unchecked"})
        @Override
        public Object intercept(Invocation invocation) throws Throwable {
            Executor executor = (Executor)invocation.getTarget();
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement)args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds)args[2];
            ResultHandler resultHandler = (ResultHandler)args[3];
            CacheKey cacheKey;
            BoundSql boundSql;
            // 4个参数
            if (args.length == 4) {
                boundSql = ms.getBoundSql(parameter);
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            }
            // 6个参数
            else {
                cacheKey = (CacheKey)args[4];
                boundSql = (BoundSql)args[5];
            }
            // 判断是否需要分页
            Page page = PageHelper.getPageThreadLocal();
            // 不执行分页
            if (page.getPageNum() <= 0) {
                return executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            // count查询
            MappedStatement countMs = newCountMappedStatement(ms);
            String sql = boundSql.getSql();
            String countSql = "select count(1) from (" + sql + ") _count";
            BoundSql countBoundSql =
                new BoundSql(ms.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
            Map<String, Object> additionalParameters = (Map<String, Object>)additionalParametersField.get(boundSql);
            for (Entry<String, Object> additionalParameter : additionalParameters.entrySet()) {
                countBoundSql.setAdditionalParameter(additionalParameter.getKey(), additionalParameter.getValue());
            }
            CacheKey countCacheKey = executor.createCacheKey(countMs, parameter, rowBounds, countBoundSql);
            Object countResult =
                executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countCacheKey, countBoundSql);
            Long count = (Long)((List)countResult).get(0);
            page.setTotal(count);
            // 分页查询
            String pageSql = sql + " limit " + page.getStartRow() + "," + page.getPageSize();
            BoundSql pageBoundSql =
                new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
            for (Entry<String, Object> additionalParameter : additionalParameters.entrySet()) {
                pageBoundSql.setAdditionalParameter(additionalParameter.getKey(), additionalParameter.getValue());
            }
            CacheKey pageCacheKey = executor.createCacheKey(ms, parameter, rowBounds, pageBoundSql);
            List listResult = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageCacheKey, pageBoundSql);
            page.addAll(listResult);
            // 清空线程局部变量分页信息
            PageHelper.pageThreadLocalClear();
            return page;
        }
    
        @Override
        public Object plugin(Object target) {
            return Plugin.wrap(target, this);
        }
    
        @Override
        public void setProperties(Properties properties) {
            try {
                additionalParametersField = BoundSql.class.getDeclaredField("additionalParameters");
                additionalParametersField.setAccessible(true);
            } catch (NoSuchFieldException | SecurityException e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 创建count的MappedStatement
         * 
         * @param ms
         * @return
         */
        private MappedStatement newCountMappedStatement(MappedStatement ms) {
            MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), ms.getId() + "_count",
                ms.getSqlSource(), ms.getSqlCommandType());
            builder.resource(ms.getResource());
            builder.fetchSize(ms.getFetchSize());
            builder.statementType(ms.getStatementType());
            builder.keyGenerator(ms.getKeyGenerator());
            if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
                StringBuilder keyProperties = new StringBuilder();
                for (String keyProperty : ms.getKeyProperties()) {
                    keyProperties.append(keyProperty).append(",");
                }
                keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
                builder.keyProperty(keyProperties.toString());
            }
            builder.timeout(ms.getTimeout());
            builder.parameterMap(ms.getParameterMap());
            // count查询返回值int
            List<ResultMap> resultMaps = new ArrayList<ResultMap>();
            ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId() + "_count", Long.class,
                new ArrayList<ResultMapping>(0)).build();
            resultMaps.add(resultMap);
            builder.resultMaps(resultMaps);
            builder.resultSetType(ms.getResultSetType());
            builder.cache(ms.getCache());
            builder.flushCacheRequired(ms.isFlushCacheRequired());
            builder.useCache(ms.isUseCache());
    
            return builder.build();
        }
    
    }

    配置

    MyBatis 配置文件增加 plugin 配置项。

    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <settings>
            <setting name="lazyLoadingEnabled" value="true"/>
            <setting name="aggressiveLazyLoading" value="false"/>
        </settings>
    
        <typeHandlers>
            <typeHandler javaType="com.yjw.demo.mybatis.common.constant.Sex"
                         jdbcType="TINYINT"
                         handler="com.yjw.demo.mybatis.common.type.SexEnumTypeHandler"/>
        </typeHandlers>
    
        <plugins>
            <plugin interceptor="com.yjw.demo.mybatis.common.page.PageInterceptor">
            </plugin>
        </plugins>
    </configuration>

    MyBatis 实用篇

    MyBatis 概念

    MyBatis 示例-简介

    MyBatis 示例-类型处理器

    MyBatis 示例-传递多个参数

    MyBatis 示例-主键回填

    MyBatis 示例-动态 SQL

    MyBatis 示例-联合查询

    MyBatis 示例-缓存

    MyBatis 示例-插件

    求关注,求点赞,《架构学习》持续更新、完善、纠正 https://www.yuque.com/yinjianwei/vyrvkf
  • 相关阅读:
    CPU使用率终极计算
    elementui
    spring security oauth2
    maven bom
    vue jsx
    [spring cloud] feign声明
    加分项
    JAVA日报
    JAVA日报
    JAVA日报
  • 原文地址:https://www.cnblogs.com/yinjw/p/11757087.html
Copyright © 2011-2022 走看看