自学内容网 自学内容网

Mybatis系列插件(一)-Mybatis-PageHelper分页插件

前言

本篇主要讲述Mybatis-PageHelper分页插件使用和其底层原理。

Spring 集成Mybatis-PageHelper

Mybatis集成篇(一)篇幅中讲了Spring集成Mybatis,在这个基础上,我们把Mybatis-PageHelper也集成进去

案例:
在原有的pom文件中增加Mybatis-PageHelper依赖

 <!--分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.0.0</version>
        </dependency>

在原有的MybatisConfig文件中增加几行代码
MybatisConfig

@Bean
    public SqlSessionFactory getSqlSessionFactory() throws Exception {
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setLogImpl(StdOutImpl.class);
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        factoryBean.setConfiguration(configuration);
        // 分页拦截器
        PageInterceptor pageInterceptor = new PageInterceptor();
        Properties properties = new Properties();
        // 这里的需要根据数据库类型设置
        properties.setProperty("helperDialect","mysql");
        pageInterceptor.setProperties(properties);
        // 添加拦截器
        configuration.addInterceptor(pageInterceptor);
        return factoryBean.getObject();
    }

Main类改造

public class Main {
    public static void main(String[] args) throws Exception {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MybatisConfig.class);
        UserMapper userMapper = applicationContext.getBean(UserMapper.class);
        //        PageHelper打开方式 start
        PageHelper.startPage(1,10);
        List<User> users = userMapper.selectUser(1);
        PageInfo<User> userPageInfo = new PageInfo<>(users);
        //        PageHelper打开方式 end
        userPageInfo.getList().forEach(System.out::println);
        users.forEach(System.out::println);
    }
}

结果:
在这里插入图片描述

Springboot 集成Mybatis-Pagehelper

Mybatis集成篇(而)篇幅中讲了Spring集成Mybatis,在这个基础上,我们把Mybatis-PageHelper也集成进去

案例:
在原有的pom文件中增加Mybatis-PageHelper依赖

 <!--分页插件-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>

ApplicationTest.test2()改造

 @Test
    public void test2() {
        PageHelper.startPage(1,10);
        List<User> users = userMapper.selectList(new LambdaQueryWrapper<User>());
        PageInfo pageInfo = new PageInfo(users);
        pageInfo.getList().forEach(System.out::println);
    }

结果:
在这里插入图片描述

原理

从第一个案例的可以看出, configuration.addInterceptor(pageInterceptor)才使得分页插件功能生效。同理,从第二个案例分析自动转配类PageHelperAutoConfiguration
在这里插入图片描述
也是增加了分页插件的PageInterceptor是,所以我们主要的入口是PageInterceptor类

PageInterceptor

@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 static final Log                            log                   = LogFactory.getLog(PageInterceptor.class);
    private static       boolean                        debug                 = false;
    protected            Cache<String, MappedStatement> msCountMap            = null;
    protected            CountMsIdGen                   countMsIdGen          = CountMsIdGen.DEFAULT;
    private volatile     Dialect                        dialect;
    private              String                         countSuffix           = "_COUNT";
    private              String                         default_dialect_class = "com.github.pagehelper.PageHelper";

    public PageInterceptor() {
        String bannerEnabled = System.getProperty("pagehelper.banner");
        if (StringUtil.isEmpty(bannerEnabled)) {
            bannerEnabled = System.getenv("PAGEHELPER_BANNER");
        }
        //默认 TRUE
        if (StringUtil.isEmpty(bannerEnabled) || Boolean.parseBoolean(bannerEnabled)) {
            log.debug("\n\n" +
                    ",------.                           ,--.  ,--.         ,--.                         \n" +
                    "|  .--. '  ,--,--.  ,---.   ,---.  |  '--'  |  ,---.  |  |  ,---.   ,---.  ,--.--. \n" +
                    "|  '--' | ' ,-.  | | .-. | | .-. : |  .--.  | | .-. : |  | | .-. | | .-. : |  .--' \n" +
                    "|  | --'  \\ '-'  | ' '-' ' \\   --. |  |  |  | \\   --. |  | | '-' ' \\   --. |  |    \n" +
                    "`--'       `--`--' .`-  /   `----' `--'  `--'  `----' `--' |  |-'   `----' `--'    \n" +
                    "                   `---'                                   `--'                        is intercepting.\n");
        }
    }

    public static boolean isDebug() {
        return debug;
    }

    /**
     * 输出启用分页方法时的调用堆栈信息
     */
    protected void debugStackTraceLog() {
        if (isDebug()) {
            Page<Object> page = PageMethod.getLocalPage();
            log.debug(page.getStackTrace());
        }
    }

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        try {
            Object[] args = invocation.getArgs();
            MappedStatement ms = (MappedStatement) args[0];
            Object parameter = args[1];
            RowBounds rowBounds = (RowBounds) args[2];
            ResultHandler resultHandler = (ResultHandler) args[3];
            Executor executor = (Executor) invocation.getTarget();
            CacheKey cacheKey;
            BoundSql boundSql;
            //由于逻辑关系,只会进入一次
            if (args.length == 4) {
                //4 个参数时
                boundSql = ms.getBoundSql(parameter);
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            } else {
                //6 个参数时
                cacheKey = (CacheKey) args[4];
                boundSql = (BoundSql) args[5];
            }
            checkDialectExists();
            //对 boundSql 的拦截处理
            if (dialect instanceof BoundSqlInterceptor.Chain) {
                boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
            }
            List resultList;
            //调用方法判断是否需要进行分页,如果不需要,直接返回结果
            // dialect默认是PageHelper,PageHelper类型会根据jdbc的url找到对应的处理类
            // 例如jdbc:mysql://116.198.234.105:3306/mybatis?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true这个url,会根据url中:xx:这样的标识找到对应的处理类,类似例子中,会找到MySqlDialect处理类
            if (!dialect.skip(ms, parameter, rowBounds)) {
                //开启debug时,输出触发当前分页执行时的PageHelper调用堆栈
                // 如果和当前调用堆栈不一致,说明在启用分页后没有消费,当前线程再次执行时消费,调用堆栈显示的方法使用不安全
                debugStackTraceLog();
                Future<Long> countFuture = null;
                //判断是否需要进行 count 查询
                if (dialect.beforeCount(ms, parameter, rowBounds)) {
                    if (dialect.isAsyncCount()) {
                        countFuture = asyncCount(ms, boundSql, parameter, rowBounds);
                    } else {
                        //查询总数
                        Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
                        //处理查询总数,返回 true 时继续分页查询,false 时直接返回
                        if (!dialect.afterCount(count, parameter, rowBounds)) {
                            //当查询总数为 0 时,直接返回空的结果
                            return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                        }
                    }
                }
                resultList = ExecutorUtil.pageQuery(dialect, executor,
                        ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
                if (countFuture != null) {
                    Long count = countFuture.get();
                    dialect.afterCount(count, parameter, rowBounds);
                }
            } else {
                //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            return dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            if (dialect != null) {
                dialect.afterAll();
            }
        }
    }

    /**
     * 异步查询总数
     */
    private Future<Long> asyncCount(MappedStatement ms, BoundSql boundSql, Object parameter, RowBounds rowBounds) {
        Configuration configuration = ms.getConfiguration();
        //异步不能复用 BoundSql,因为分页使用时会添加分页参数,这里需要复制一个新的
        BoundSql countBoundSql = new BoundSql(configuration, boundSql.getSql(), new ArrayList<>(boundSql.getParameterMappings()), parameter);
        Map<String, Object> additionalParameter = ExecutorUtil.getAdditionalParameter(boundSql);
        if (additionalParameter != null){
            for (String key : additionalParameter.keySet()) {
                countBoundSql.setAdditionalParameter(key, additionalParameter.get(key));
            }
        }
        //异步想要起作用需要新的数据库连接,需要独立的事务,创建新的Executor,因此异步查询只适合在独立查询中使用,如果混合增删改操作,不能开启异步
        Environment environment = configuration.getEnvironment();
        TransactionFactory transactionFactory = null;
        if (environment == null || environment.getTransactionFactory() == null) {
            transactionFactory = new ManagedTransactionFactory();
        } else {
            transactionFactory = environment.getTransactionFactory();
        }
        //创建新的事务
        Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), null, false);
        //使用新的 Executor 执行 count 查询,这里没有加载拦截器,避免递归死循环
        Executor countExecutor = new CachingExecutor(new SimpleExecutor(configuration, tx));

        return dialect.asyncCountTask(() -> {
            try {
                return count(countExecutor, ms, parameter, rowBounds, null, countBoundSql);
            } finally {
                tx.close();
            }
        });
    }

    /**
     * Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化
     * <p>
     * 因此这里会出现 null 的情况 fixed #26
     */
    private void checkDialectExists() {
        if (dialect == null) {
            synchronized (default_dialect_class) {
                if (dialect == null) {
                    setProperties(new Properties());
                }
            }
        }
    }

    private Long count(Executor executor, MappedStatement ms, Object parameter,
                       RowBounds rowBounds, ResultHandler resultHandler,
                       BoundSql boundSql) throws SQLException {
        String countMsId = countMsIdGen.genCountMsId(ms, parameter, boundSql, countSuffix);
        Long count;
        //先判断是否存在手写的 count 查询
        MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
        if (countMs != null) {
            count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
        } else {
            if (msCountMap != null) {
                countMs = msCountMap.get(countMsId);
            }
            //自动创建
            if (countMs == null) {
                //根据当前的 ms 创建一个返回值为 Long 类型的 ms
                countMs = MSUtils.newCountMappedStatement(ms, countMsId);
                if (msCountMap != null) {
                    msCountMap.put(countMsId, countMs);
                }
            }
            count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
        }
        return count;
    }

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

    @Override
    public void setProperties(Properties properties) {
        //缓存 count ms
        msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
        String dialectClass = properties.getProperty("dialect");
        if (StringUtil.isEmpty(dialectClass)) {
            dialectClass = default_dialect_class;
        }
        Dialect tempDialect = ClassUtil.newInstance(dialectClass, properties);
        tempDialect.setProperties(properties);

        String countSuffix = properties.getProperty("countSuffix");
        if (StringUtil.isNotEmpty(countSuffix)) {
            this.countSuffix = countSuffix;
        }

        // debug模式,用于排查不安全分页调用
        debug = Boolean.parseBoolean(properties.getProperty("debug"));

        // 通过 countMsId 配置自定义类
        String countMsIdGenClass = properties.getProperty("countMsIdGen");
        if (StringUtil.isNotEmpty(countMsIdGenClass)) {
            countMsIdGen = ClassUtil.newInstance(countMsIdGenClass, properties);
        }
        // 初始化完成后再设置值,保证 dialect 完成初始化
        dialect = tempDialect;
    }
}

PageHelper

/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2014-2023 abel533@gmail.com
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */

package com.github.pagehelper;

import com.github.pagehelper.dialect.AbstractHelperDialect;
import com.github.pagehelper.page.PageAutoDialect;
import com.github.pagehelper.page.PageBoundSqlInterceptors;
import com.github.pagehelper.page.PageMethod;
import com.github.pagehelper.page.PageParams;
import com.github.pagehelper.parser.CountSqlParser;
import com.github.pagehelper.util.StringUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.RowBounds;

import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.Future;

/**
 * Mybatis - 通用分页拦截器<br/>
 * 项目地址 : http://git.oschina.net/free/Mybatis_PageHelper
 *
 * @author liuzh/abel533/isea533
 * @version 5.0.0
 */
public class PageHelper extends PageMethod implements Dialect, BoundSqlInterceptor.Chain {
    private PageParams               pageParams;
    private PageAutoDialect          autoDialect;
    private PageBoundSqlInterceptors pageBoundSqlInterceptors;
    private ForkJoinPool             asyncCountService;

    @Override
    public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
        Page page = pageParams.getPage(parameterObject, rowBounds);
        if (page == null) {
            return true;
        } else {
            //设置默认的 count 列
            if (StringUtil.isEmpty(page.getCountColumn())) {
                page.setCountColumn(pageParams.getCountColumn());
            }
            //设置默认的异步 count 设置
            if (page.getAsyncCount() == null) {
                page.setAsyncCount(pageParams.isAsyncCount());
            }
            autoDialect.initDelegateDialect(ms, page.getDialectClass());
            return false;
        }
    }

    @Override
    public boolean isAsyncCount() {
        return getLocalPage().asyncCount();
    }

    @Override
    public <T> Future<T> asyncCountTask(Callable<T> task) {
        //异步执行时需要将ThreadLocal值传递,否则会找不到
        AbstractHelperDialect dialectThreadLocal = autoDialect.getDialectThreadLocal();
        Page<Object> localPage = getLocalPage();
        String countId = UUID.randomUUID().toString();
        return asyncCountService.submit(() -> {
            try {
                //设置 ThreadLocal
                autoDialect.setDialectThreadLocal(dialectThreadLocal);
                setLocalPage(localPage);
                return task.call();
            } finally {
                autoDialect.clearDelegate();
                clearPage();
            }
        });
    }

    @Override
    public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
        return autoDialect.getDelegate().beforeCount(ms, parameterObject, rowBounds);
    }

    @Override
    public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
        return autoDialect.getDelegate().getCountSql(ms, boundSql, parameterObject, rowBounds, countKey);
    }

    @Override
    public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) {
        return autoDialect.getDelegate().afterCount(count, parameterObject, rowBounds);
    }

    @Override
    public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
        return autoDialect.getDelegate().processParameterObject(ms, parameterObject, boundSql, pageKey);
    }

    @Override
    public boolean beforePage(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
        return autoDialect.getDelegate().beforePage(ms, parameterObject, rowBounds);
    }

    @Override
    public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
        return autoDialect.getDelegate().getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);
    }

    public String getPageSql(String sql, Page page, RowBounds rowBounds, CacheKey pageKey) {
        return autoDialect.getDelegate().getPageSql(sql, page, pageKey);
    }

    @Override
    public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
        //这个方法即使不分页也会被执行,所以要判断 null
        AbstractHelperDialect delegate = autoDialect.getDelegate();
        if (delegate != null) {
            return delegate.afterPage(pageList, parameterObject, rowBounds);
        }
        return pageList;
    }

    @Override
    public void afterAll() {
        //这个方法即使不分页也会被执行,所以要判断 null
        AbstractHelperDialect delegate = autoDialect.getDelegate();
        if (delegate != null) {
            delegate.afterAll();
            autoDialect.clearDelegate();
        }
        clearPage();
    }

    @Override
    public BoundSql doBoundSql(BoundSqlInterceptor.Type type, BoundSql boundSql, CacheKey cacheKey) {
        Page<Object> localPage = getLocalPage();
        BoundSqlInterceptor.Chain chain = localPage != null ? localPage.getChain() : null;
        if (chain == null) {
            BoundSqlInterceptor boundSqlInterceptor = localPage != null ? localPage.getBoundSqlInterceptor() : null;
            BoundSqlInterceptor.Chain defaultChain = pageBoundSqlInterceptors != null ? pageBoundSqlInterceptors.getChain() : null;
            if (boundSqlInterceptor != null) {
                chain = new BoundSqlInterceptorChain(defaultChain, Arrays.asList(boundSqlInterceptor));
            } else if (defaultChain != null) {
                chain = defaultChain;
            }
            if (chain == null) {
                chain = DO_NOTHING;
            }
            if (localPage != null) {
                localPage.setChain(chain);
            }
        }
        return chain.doBoundSql(type, boundSql, cacheKey);
    }

    @Override
    public void setProperties(Properties properties) {
        setStaticProperties(properties);
        pageParams = new PageParams();
        autoDialect = new PageAutoDialect();
        pageBoundSqlInterceptors = new PageBoundSqlInterceptors();
        pageParams.setProperties(properties);
        autoDialect.setProperties(properties);
        pageBoundSqlInterceptors.setProperties(properties);
        //20180902新增 aggregateFunctions, 允许手动添加聚合函数(影响行数)
        CountSqlParser.addAggregateFunctions(properties.getProperty("aggregateFunctions"));
        // 异步 asyncCountService 并发度设置,这里默认为应用可用的处理器核心数 * 2,更合理的值应该综合考虑数据库服务器的处理能力
        int asyncCountParallelism = Integer.parseInt(properties.getProperty("asyncCountParallelism",
                "" + (Runtime.getRuntime().availableProcessors() * 2)));
        asyncCountService = new ForkJoinPool(asyncCountParallelism,
                pool -> {
                    final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
                    worker.setName("pagehelper-async-count-" + worker.getPoolIndex());
                    return worker;
                }, null, true);
    }
}

那本篇到这里就结束了。


原文地址:https://blog.csdn.net/YaYicho/article/details/144329031

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!