你知道的越多,不知道的就越多,业余的像一棵小草!

你来,我们一起精进!你不来,我和你的竞争对手一起精进!

编辑:业余草

urlify.cn/z2IFn2

推荐:https://www.xttblog.com/?p=5116

PageHelper是一款好用的开源免费的Mybatis第三方物理分页插件,其实我并不想加上好用两个字,但是为了表扬插件作者开源免费的崇高精神,我毫不犹豫的加上了好用一词作为赞美。

原本以为分页插件,应该是很简单的,然而PageHelper比我想象的要复杂许多,它做的很强大,也很彻底,强大到使用者可能并不需要这么多功能,彻底到一参可以两用。但是,我认为,作为分页插件,完成物理分页任务是根本,其它的很多智能并不是必要的,保持它够傻够憨,专业术语叫stupid,简单就是美。

我们将简单介绍PageHelper的基本使用和配置参数的含义,重点分析PageHelper作为Mybatis分页插件的实现原理。

1. PageHelper的maven依赖及插件配置

  com.github.pagehelper  pagehelper  4.1.6

PageHelper除了本身的jar包外,它还依赖了一个叫jsqlparser的jar包,使用时,我们不需要单独指定jsqlparser的maven依赖,maven的间接依赖会帮我们引入。

                                    

上面是PageHelper官方给的配置和注释,虽然写的很多,不过确实描述的很明白。

dialect:标识是哪一种数据库,设计上必须。

offsetAsPageNum:将RowBounds第一个参数offset当成pageNum页码使用,这就是上面说的一参两用,个人觉得完全没必要,offset = pageSize * pageNum就搞定了,何必混用参数呢?

rowBoundsWithCount:设置为true时,使用RowBounds分页会进行count查询,个人觉得完全没必要,实际开发中,每一个列表分页查询,都配备一个count数量查询即可。

reasonable:value=true时,pageNum小于1会查询第一页,如果pageNum大于pageSize会查询最后一页 ,个人认为,参数校验在进入Mybatis业务体系之前,就应该完成了,不可能到达Mybatis业务体系内参数还带有非法的值。

这么一来,我们只需要记住 dialect = mysql 一个参数即可,其实,还有下面几个相关参数可以配置。

autoDialect:true or false,是否自动检测dialect。

autoRuntimeDialect:true or false,多数据源时,是否自动检测dialect。

closeConn:true or false,检测完dialect后,是否关闭Connection连接。

上面这3个智能参数,不到万不得已,我们不应该在系统中使用,我们只需要一个dialect = mysql 或者 dialect = oracle就够了,如果系统中需要使用,还是得问问自己,是否真的非用不可。

2. PageHelper源码分析

@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))public class PageHelper implements Interceptor {    //sql工具类    private SqlUtil sqlUtil;    //属性参数信息    private Properties properties;    //配置对象方式    private SqlUtilConfig sqlUtilConfig;    //自动获取dialect,如果没有setProperties或setSqlUtilConfig,也可以正常进行    private boolean autoDialect = true;    //运行时自动获取dialect    private boolean autoRuntimeDialect;    //多数据源时,获取jdbcurl后是否关闭数据源    private boolean closeConn = true;    //缓存    private Map urlSqlUtilMap = new ConcurrentHashMap();    private ReentrantLock lock = new ReentrantLock();// ...}

上面是官方源码以及源码所带的注释,我们再补充一下。

SqlUtil:数据库类型专用sql工具类,一个数据库url对应一个SqlUtil实例,SqlUtil内有一个Parser对象,如果是mysql,它是MysqlParser,如果是oracle,它是OracleParser,这个Parser对象是SqlUtil不同实例的主要存在价值。执行count查询、设置Parser对象、执行分页查询、保存Page分页对象等功能,均由SqlUtil来完成。

SqlUtilConfig:Spring Boot中使用,忽略。

autoRuntimeDialect:多个数据源切换时,比如mysql和oracle数据源同时存在,就不能简单指定dialect,这个时候就需要运行时自动检测当前的dialect。

Map urlSqlUtilMap:它就用来缓存autoRuntimeDialect自动检测结果的,key是数据库的url,value是SqlUtil。由于这种自动检测只需要执行1次,所以做了缓存。

ReentrantLock lock:这个lock对象是比较有意思的现象,urlSqlUtilMap明明是一个同步ConcurrentHashMap,又搞了一个lock出来同步ConcurrentHashMap做什么呢?是否是画蛇添足?在《Java并发编程实战》一书中有详细论述,简单的说,ConcurrentHashMap可以保证put或者remove方法一定是线程安全的,但它不能保证put、get、remove的组合操作是线程安全的,为了保证组合操作也是线程安全的,所以使用了lock。

com.github.pagehelper.PageHelper.java源码。

   // Mybatis拦截器方法    public Object intercept(Invocation invocation) throws Throwable {        if (autoRuntimeDialect) {            // 多数据源            SqlUtil sqlUtil = getSqlUtil(invocation);            return sqlUtil.processPage(invocation);        } else {            // 单数据源            if (autoDialect) {                initSqlUtil(invocation);            }            // 指定了dialect            return sqlUtil.processPage(invocation);        }    }    public synchronized void initSqlUtil(Invocation invocation) {        if (this.sqlUtil == null) {            this.sqlUtil = getSqlUtil(invocation);            if (!autoRuntimeDialect) {                properties = null;                sqlUtilConfig = null;            }            autoDialect = false;        }    }    public void setProperties(Properties p) {        checkVersion();        //多数据源时,获取jdbcurl后是否关闭数据源        String closeConn = p.getProperty("closeConn");        //解决#97        if(StringUtil.isNotEmpty(closeConn)){            this.closeConn = Boolean.parseBoolean(closeConn);        }        //初始化SqlUtil的PARAMS        SqlUtil.setParams(p.getProperty("params"));        //数据库方言        String dialect = p.getProperty("dialect");        String runtimeDialect = p.getProperty("autoRuntimeDialect");        if (StringUtil.isNotEmpty(runtimeDialect) && runtimeDialect.equalsIgnoreCase("TRUE")) {            this.autoRuntimeDialect = true;            this.autoDialect = false;            this.properties = p;        } else if (StringUtil.isEmpty(dialect)) {            autoDialect = true;            this.properties = p;        } else {            autoDialect = false;            sqlUtil = new SqlUtil(dialect);            sqlUtil.setProperties(p);        }    }    public SqlUtil getSqlUtil(Invocation invocation) {        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];        //改为对dataSource做缓存        DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource();        String url = getUrl(dataSource);        if (urlSqlUtilMap.containsKey(url)) {            return urlSqlUtilMap.get(url);        }        try {            lock.lock();            if (urlSqlUtilMap.containsKey(url)) {                return urlSqlUtilMap.get(url);            }            if (StringUtil.isEmpty(url)) {                throw new RuntimeException("无法自动获取jdbcUrl,请在分页插件中配置dialect参数!");            }            String dialect = Dialect.fromJdbcUrl(url);            if (dialect == null) {                throw new RuntimeException("无法自动获取数据库类型,请通过dialect参数指定!");            }            SqlUtil sqlUtil = new SqlUtil(dialect);            if (this.properties != null) {                sqlUtil.setProperties(properties);            } else if (this.sqlUtilConfig != null) {                sqlUtil.setSqlUtilConfig(this.sqlUtilConfig);            }            urlSqlUtilMap.put(url, sqlUtil);            return sqlUtil;        } finally {            lock.unlock();        }    }

autoRuntimeDialect:多数据源,会创建多个SqlUtil。

autoDialect:单数据源,只会创建1个SqlUtil。单数据源时,也可以当做多数据源来使用。

指定了dialect:只会创建1个SqlUtil。

3. PageSqlSource

public abstract class PageSqlSource implements SqlSource { /**     * 获取正常的BoundSql     *     * @param parameterObject     * @return     */    protected abstract BoundSql getDefaultBoundSql(Object parameterObject);    /**     * 获取Count查询的BoundSql     *     * @param parameterObject     * @return     */    protected abstract BoundSql getCountBoundSql(Object parameterObject);    /**     * 获取分页查询的BoundSql     *     * @param parameterObject     * @return     */    protected abstract BoundSql getPageBoundSql(Object parameterObject);    /**     * 获取BoundSql     *     * @param parameterObject     * @return     */    @Override    public BoundSql getBoundSql(Object parameterObject) {        Boolean count = getCount();        if (count == null) {            return getDefaultBoundSql(parameterObject);        } else if (count) {            return getCountBoundSql(parameterObject);        } else {            return getPageBoundSql(parameterObject);        }    }}

getDefaultBoundSql:获取原始的未经改造的BoundSql。

getCountBoundSql:不需要写count查询,插件根据分页查询sql,智能的为你生成的count查询BoundSql。

getPageBoundSql:获取分页查询的BoundSql。

举例:

DefaultBoundSql:select  stud_id as studId , name, email, dob, phone from students

CountBoundSql:select  count(0) from students --由PageHelper智能完成

PageBoundSql:select  stud_id as studId , name, email, dob, phone from students limit ?, ?

(Made In Intellij Idea IDE)

public class PageStaticSqlSource extends PageSqlSource {    private String sql;    private List parameterMappings;    private Configuration configuration;    private SqlSource original;    @Override    protected BoundSql getDefaultBoundSql(Object parameterObject) {        String tempSql = sql;        String orderBy = PageHelper.getOrderBy();        if (orderBy != null) {            tempSql = OrderByParser.converToOrderBySql(sql, orderBy);        }        return new BoundSql(configuration, tempSql, parameterMappings, parameterObject);    }    @Override    protected BoundSql getCountBoundSql(Object parameterObject) {        // localParser指的就是MysqlParser或者OracleParser        // localParser.get().getCountSql(sql),可以根据原始的sql,生成一个count查询的sql        return new BoundSql(configuration, localParser.get().getCountSql(sql), parameterMappings, parameterObject);    }    @Override    protected BoundSql getPageBoundSql(Object parameterObject) {        String tempSql = sql;        String orderBy = PageHelper.getOrderBy();        if (orderBy != null) {            tempSql = OrderByParser.converToOrderBySql(sql, orderBy);        }        // getPageSql可以根据原始的sql,生成一个带有分页参数信息的sql,比如 limit ?, ?        tempSql = localParser.get().getPageSql(tempSql);        // 由于sql增加了分页参数的?号占位符,getPageParameterMapping()就是在原有List基础上,增加两个分页参数对应的ParameterMapping对象,为分页参数赋值使用        return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject);    }}

假设List原来的size=2,添加分页参数后,其size=4,具体增加多少个,看分页参数的?号数量。

其他PageSqlSource,原理和PageStaticSqlSource一模一样。

解析sql,并增加分页参数占位符,或者生成count查询的sql,都依靠Parser来完成。

4. com.github.pagehelper.parser.Parser

(Made In Intellij Idea IDE)

public class MysqlParser extends AbstractParser {    @Override    public String getPageSql(String sql) {        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);        sqlBuilder.append(sql);        sqlBuilder.append(" limit ?,?");        return sqlBuilder.toString();    }    @Override    public Map setPageParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql, Page> page) {        Map paramMap = super.setPageParameter(ms, parameterObject, boundSql, page);        paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());        paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());        return paramMap;    }}

我们可以清楚的看到,MysqlParser是如何添加分页占位符和分页参数的。

public abstract class AbstractParser implements Parser, Constant {    public String getCountSql(final String sql) {        return sqlParser.getSmartCountSql(sql);    }}

生成count sql,则是前文提到的jsqlparser工具包来完成的,是另外一个开源的sql解析工具包。

5. SqlUtil.doProcessPage()分页查询

// PageSqlSource装饰原SqlSource   public void processMappedStatement(MappedStatement ms) throws Throwable {        SqlSource sqlSource = ms.getSqlSource();        MetaObject msObject = SystemMetaObject.forObject(ms);        SqlSource pageSqlSource;        if (sqlSource instanceof StaticSqlSource) {            pageSqlSource = new PageStaticSqlSource((StaticSqlSource) sqlSource);        } else if (sqlSource instanceof RawSqlSource) {            pageSqlSource = new PageRawSqlSource((RawSqlSource) sqlSource);        } else if (sqlSource instanceof ProviderSqlSource) {            pageSqlSource = new PageProviderSqlSource((ProviderSqlSource) sqlSource);        } else if (sqlSource instanceof DynamicSqlSource) {            pageSqlSource = new PageDynamicSqlSource((DynamicSqlSource) sqlSource);        } else {            throw new RuntimeException("无法处理该类型[" + sqlSource.getClass() + "]的SqlSource");        }        msObject.setValue("sqlSource", pageSqlSource);        //由于count查询需要修改返回值,因此这里要创建一个Count查询的MS        msCountMap.put(ms.getId(), MSUtils.newCountMappedStatement(ms));    }// 执行分页查询private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {        //保存RowBounds状态        RowBounds rowBounds = (RowBounds) args[2];        //获取原始的ms        MappedStatement ms = (MappedStatement) args[0];        //判断并处理为PageSqlSource        if (!isPageSqlSource(ms)) {            processMappedStatement(ms);        }        //设置当前的parser,后面每次使用前都会set,ThreadLocal的值不会产生不良影响        ((PageSqlSource)ms.getSqlSource()).setParser(parser);        try {            //忽略RowBounds-否则会进行Mybatis自带的内存分页            args[2] = RowBounds.DEFAULT;            //如果只进行排序 或 pageSizeZero的判断            if (isQueryOnly(page)) {                return doQueryOnly(page, invocation);            }            //简单的通过total的值来判断是否进行count查询            if (page.isCount()) {                page.setCountSignal(Boolean.TRUE);                //替换MS                args[0] = msCountMap.get(ms.getId());                //查询总数                Object result = invocation.proceed();                //还原ms                args[0] = ms;                //设置总数                page.setTotal((Integer) ((List) result).get(0));                if (page.getTotal() == 0) {                    return page;                }            } else {                page.setTotal(-1l);            }            //pageSize>0的时候执行分页查询,pageSize<=0的时候不执行相当于可能只返回了一个count            if (page.getPageSize() > 0 &&                    ((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0)                            || rowBounds != RowBounds.DEFAULT)) {                //将参数中的MappedStatement替换为新的qs                page.setCountSignal(null);                BoundSql boundSql = ms.getBoundSql(args[1]);                args[1] = parser.setPageParameter(ms, args[1], boundSql, page);                page.setCountSignal(Boolean.FALSE);                //执行分页查询                Object result = invocation.proceed();                //得到处理结果                page.addAll((List) result);            }        } finally {            ((PageSqlSource)ms.getSqlSource()).removeParser();        }        //返回结果        return page;    }

源码中注意关键的四点即可:

1、msCountMap.put(ms.getId(), MSUtils.newCountMappedStatement(ms)),创建count查询的MappedStatement对象,并缓存于msCountMap。

2、如果count=true,则执行count查询,结果total值保存于page对象中,继续执行分页查询。

3、执行分页查询,将查询结果保存于page对象中,page是一个ArrayList对象。

4、
args[2] = RowBounds.DEFAULT,改变Mybatis原有分页行为;

args[1] = parser.setPageParameter(ms, args[1], boundSql, page),改变原有参数列表(增加分页参数)。

6. PageHelper的两种使用方式

第一种、直接通过RowBounds参数完成分页查询 。

List list = studentMapper.find(new RowBounds(0, 10));Page page = ((Page) list;

第二种、PageHelper.startPage()静态方法

//获取第1页,10条内容,默认查询总数count    PageHelper.startPage(1, 10);//紧跟着的第一个select方法会被分页    List list = studentMapper.find();    Page page = ((Page) list;

注:返回结果list,已经是Page对象,Page对象是一个ArrayList。

原理:使用ThreadLocal来传递和保存Page对象,每次查询,都需要单独设置PageHelper.startPage()方法。

public class SqlUtil implements Constant {    private static final ThreadLocal LOCAL_PAGE = new ThreadLocal();}

本文中经常提到的count查询,其实是PageHelper帮助我们生成的一个MappedStatement内存对象,它可以免去我们在XXXMapper.xml内单独声明一个sql count查询,我们只需要写一个sql分页业务查询即可。

PageHelper使用建议(性能最好):

  • 明确指定dialect。

  • 明确编写sql分页业务和与它对应的count查询,别图省事。

mybatis 分页需要的jar包下载_牛逼哄哄的PageHelper分页插件到底牛在哪里?相关推荐

  1. mybatis 分页需要的jar包下载_064、MyBatis

    一.什么是框架 a)框架是偷懒的程序员将代码进行封装, 之后进行重复使用的过程. b)框架其实是一个半成品, 以连接数据库为例, 连接数据库使用的驱动, url, 用户名, 密码必须告知框架. c)程 ...

  2. java word jar_处理word的poi的jar包下载_处理word的poi的jar包官方下载-太平洋下载中心...

    对word文档的处理,提供对word信息抽取的类. 对word处理的poi的jar包(poi-bin-3.2-FINAL-20081019.zip) 现在Java对word excel进行操作的jar ...

  3. Mybatis的jar包下载地址

    Mybatis jar包下载地址:https://github.com/mybatis/mybatis-3/releases 与SpringMVC进行整合jar包下载地址:https://github ...

  4. hookup_2.10-0.2.3.jar包下载

    hookup_2.10-0.2.3.jar包下载地址,自己也做一个记录.同一时候也给须要的朋友提供一个方便,希望对大家有所帮助.下载地址:http://www.59biye.com/jar/cont/ ...

  5. idea中的pom文件中的jar包下载不了,手动下载jar包的方法

    问题描述: 在pom文件中添加依赖的时候,程序怎么着都是下载不了,而且实验了各种方式: IDEA引MAVEN项目jar包依赖导入问题解决 https://www.cnblogs.com/a845701 ...

  6. jackson的jar包下载

    没必要去csdn的下载频道去付付费下载 这里有最全最新的Jackson的jar包资源 jackson的jar包下载

  7. Maven远程仓库:pom依赖以及jar包下载

    Maven远程仓库:pom依赖xml配置以及jar包下载: 地址1: http://mvnrepository.com/ 地址2: http://172.16.163.52:8081/nexus/#w ...

  8. spring mvc学习(2):spring jar包下载

    jstl的jar包的下载 我们在使用spring框架的时候导入jstl标签库需要使用到jstl的jar包,假如没有加入到eclipse的lib目录下,使用alt + /的时候不会有提示,所以我们需要把 ...

  9. idea maven 仓库 jar 包下载不来下解决方案

    每次换一个新的环境写代码的时候,用 idea下载 maven 依赖 jar 包都是我无法言语的痛 像这样 或者是这样 maven总是能找到各种 jar 包让你下载不下来 经过无数次痛苦的尝试之后,终于 ...

最新文章

  1. 关于锂离子法拉电容的短路实验
  2. 网络推广期间遇到页面无效收录情况网络推广专员如何应对?
  3. .NET 4.0 任务(Task)
  4. 给python小白的几个小练习(附答案详解哦)
  5. [国嵌攻略][125][总线设备驱动模型]
  6. Java基础学习总结(111)——Java虚拟机JVM总结大全
  7. Mac删除Windows10后空间丢失解决
  8. java中Cookie类详解
  9. 封装kmalloc/malloc的一些小函数
  10. 软考高级《信息系统项目管理师》(简称高项)考证经验(满满的干货)
  11. mysql数据库面试题学生表_SQL笔试题:下面是学生表(student)的结构说明
  12. Firefox浏览器如何安装Alexa工具条
  13. Win/Chomer美化
  14. java识别验证码图片_Java识别图像、验证码
  15. oracle查询语句 switch,ORACLE SQL语句中的“SWITCH语句”函数DECODE
  16. 深度学习之文本摘要自动生成
  17. 查看jvm进程cpu火焰图工具
  18. Binder源码阅读指南之java层,作为Android开发程序员
  19. 如何基于知识图谱技术构建现代搜索引擎系统、智能问答系统、智能推荐系统?
  20. 超级推荐:网工必备模拟器PNETLab,附下载链接,全球第一篇最优质的帖子

热门文章

  1. 【AI】人工智能深度学习入门路线
  2. 4.3 核对矩阵的维数-深度学习-Stanford吴恩达教授
  3. Latex安装中知道的基础常识
  4. 期末不挂科のJAVA
  5. Prettier 1.15代码格式化工具新增Angular和Vue.js支持
  6. 这个男人让你的爬虫开发效率提升8倍
  7. 项目遇到的问题或处理办法
  8. Android 中文 API (19) —— TwoLineListItem
  9. 卡巴斯基:乌云反思 企业安全堪忧
  10. OpenCV学习笔记——Mat类型数据存储