PageHelper分页原理源码分析

Posted kuku_zhongzi

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PageHelper分页原理源码分析相关的知识,希望对你有一定的参考价值。

一、PageHelper的配置加载及使用

1.1 配置

参考地址 https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

在 pom.xml 中添加如下依赖:

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>5.3.1</version>
</dependency>

在 yml 配置文件中添加如下依赖:

pagehelper:
  helperDialect: mysql #数据库类型,不指定的话会解析 datasource.url进行配置
  supportMethodsArguments: true
  params: count=countSql 

1.2 启动加载流程

SpringBoot项目启动时pagehelper的加载流程

// 核心类 这里继承了myabtis的拦截器,会在项目启动时加载此类
public class PageInterceptor implements Interceptor 
	// 构造方法 读取配置输出banner
    public PageInterceptor() 
        String bannerEnabled = System.getProperty("pagehelper.banner");
        if (StringUtil.isEmpty(bannerEnabled)) 
            bannerEnabled = System.getenv("PAGEHELPER_BANNER");
        

        if (StringUtil.isEmpty(bannerEnabled) || Boolean.parseBoolean(bannerEnabled)) 
            log.debug("...");
        
    
    // 核心拦截方法
    public Object intercept(Invocation invocation) throws Throwable 
    	// 代码省略....
    
    // 统计方法
    private Long count(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException 
    	// 代码省略....
    
    // 读取配置,设置属性
    public void setProperties(Properties properties) 
	     this.msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
	     // 如果没有配置dialect属性的话,会默认实例化PageHelper类
	     String dialectClass = properties.getProperty("dialect");
	     if (StringUtil.isEmpty(dialectClass)) 
	         dialectClass = this.default_dialect_class;
	     
	     try 
		     // 实例化PageHelper
		     // default_dialect_class =>  com.github.pagehelper.PageHelper
	         Class<?> aClass = Class.forName(dialectClass);
	         this.dialect = (Dialect)aClass.newInstance();
	      catch (Exception var4) 
	         throw new PageException(var4);
	     
	     this.dialect.setProperties(properties);
	     String countSuffix = properties.getProperty("countSuffix");
	     if (StringUtil.isNotEmpty(countSuffix)) 
	         this.countSuffix = countSuffix;
	     
 

进入com.github.pagehelper.PageHelper实例化过程

	// PageHelper的设置属性方法...读取配置,设置属性
    public void setProperties(Properties properties) 
        setStaticProperties(properties);
        this.pageParams = new PageParams();
        this.autoDialect = new PageAutoDialect();
        this.pageBoundSqlInterceptors = new PageBoundSqlInterceptors();
        this.pageParams.setProperties(properties);
        // 这里会根据配置文件中的helperDialect属性进行实例化方言实现类
        this.autoDialect.setProperties(properties);
        this.pageBoundSqlInterceptors.setProperties(properties);
        CountSqlParser.addAggregateFunctions(properties.getProperty("aggregateFunctions"));
    

进入com.github.pagehelper.page.PageAutoDialect方言配置

	// 1.进入 PageAutoDialect的设置属性方法...读取配置,设置属性
	public void setProperties(Properties properties) 
        this.initAutoDialectClass(properties);
        String useSqlserver2012 = properties.getProperty("useSqlserver2012");
        if (StringUtil.isNotEmpty(useSqlserver2012) && Boolean.parseBoolean(useSqlserver2012)) 
            registerDialectAlias("sqlserver", SqlServer2012Dialect.class);
            registerDialectAlias("sqlserver2008", SqlServerDialect.class);
        
        this.initDialectAlias(properties);
        // 读取方言配置
        String dialect = properties.getProperty("helperDialect");
        String runtimeDialect = properties.getProperty("autoRuntimeDialect");
        if (StringUtil.isNotEmpty(runtimeDialect) && "TRUE".equalsIgnoreCase(runtimeDialect)) 
            this.autoDialect = false;
            this.properties = properties;
         else if (StringUtil.isEmpty(dialect)) 
            this.autoDialect = true;
            this.properties = properties;
         else 
            this.autoDialect = false;
            this.delegate = instanceDialect(dialect, properties);
        
    
	// 静态方法,PageAutoDialect加载时会先把以下所有配置加入变量dialectAliasMap
    static 
        registerDialectAlias("hsqldb", HsqldbDialect.class);
        registerDialectAlias("h2", HsqldbDialect.class);
        registerDialectAlias("phoenix", HsqldbDialect.class);
        registerDialectAlias("postgresql", PostgreSqlDialect.class);
        registerDialectAlias("mysql", MySqlDialect.class);
        registerDialectAlias("mariadb", MySqlDialect.class);
        registerDialectAlias("sqlite", MySqlDialect.class);
        registerDialectAlias("herddb", HerdDBDialect.class);
        registerDialectAlias("oracle", OracleDialect.class);
        registerDialectAlias("oracle9i", Oracle9iDialect.class);
        registerDialectAlias("db2", Db2Dialect.class);
        registerDialectAlias("as400", AS400Dialect.class);
        registerDialectAlias("informix", InformixDialect.class);
        registerDialectAlias("informix-sqli", InformixDialect.class);
        registerDialectAlias("sqlserver", SqlServerDialect.class);
        registerDialectAlias("sqlserver2012", SqlServer2012Dialect.class);
        registerDialectAlias("derby", SqlServer2012Dialect.class);
        registerDialectAlias("dm", OracleDialect.class);
        registerDialectAlias("edb", OracleDialect.class);
        registerDialectAlias("oscar", OscarDialect.class);
        registerDialectAlias("clickhouse", MySqlDialect.class);
        registerDialectAlias("highgo", HsqldbDialect.class);
        registerDialectAlias("xugu", HsqldbDialect.class);
        registerDialectAlias("impala", HsqldbDialect.class);
        registerDialectAlias("firebirdsql", FirebirdDialect.class);
        registerAutoDialectAlias("old", DefaultAutoDialect.class);
        registerAutoDialectAlias("hikari", HikariAutoDialect.class);
        registerAutoDialectAlias("druid", DruidAutoDialect.class);
        registerAutoDialectAlias("tomcat-jdbc", TomcatAutoDialect.class);
        registerAutoDialectAlias("dbcp", DbcpAutoDialect.class);
        registerAutoDialectAlias("c3p0", C3P0AutoDialect.class);
        registerAutoDialectAlias("default", DataSourceNegotiationAutoDialect.class);
    
    // 2. 实例化具体方言
    public static AbstractHelperDialect instanceDialect(String dialectClass, Properties properties) 
        if (StringUtil.isEmpty(dialectClass)) 
            throw new PageException("使用 PageHelper 分页插件时,必须设置 helper 属性");
         else 
            AbstractHelperDialect dialect;
            try 
            	// 这里会去dialectAliasMap取值然后获取对应的class
                Class sqlDialectClass = resloveDialectClass(dialectClass);
                if (!AbstractHelperDialect.class.isAssignableFrom(sqlDialectClass)) 
                    throw new PageException("使用 PageHelper 时,方言必须是实现 " + AbstractHelperDialect.class.getCanonicalName() + " 接口的实现类!");
                
				// 实例化
                dialect = (AbstractHelperDialect)sqlDialectClass.newInstance();
             catch (Exception var4) 
                throw new PageException("初始化 helper [" + dialectClass + "]时出错:" + var4.getMessage(), var4);
            

            dialect.setProperties(properties);
            return dialect;
     	
   

1.3 使用

// 开启分页
PageUtils.startPage(1,10);
/**
 * 开始分页
 *
 * @param pageNum      页码
 * @param pageSize     每页显示数量
 * @param count        是否进行count查询
 * @param reasonable   分页合理化,null时用默认配置
 * @param pageSizeZero true且pageSize=0时返回全部结果,false时分页,null时用默认配置
 */
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) 
    Page<E> page = new Page(pageNum, pageSize, count);
    // 这里会通过pageNum和pageSize计算起始行
    page.setReasonable(reasonable);
    page.setPageSizeZero(pageSizeZero);
    //当已经执行过orderBy的时候
    Page<E> oldPage = getLocalPage();
    if (oldPage != null && oldPage.isOrderByOnly()) 
        page.setOrderBy(oldPage.getOrderBy());
    
    //把当前分页参数保存到ThreadLocal
    setLocalPage(page);
    return page;

至此,分页前的准备已经做好了,接着进行正常的查询操作。

二、分页流程

2.3 PageInterceptor拦截器

开启分页之后,所有的select请求都会被PageInterceptor捕获,从而进行分页

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) 
              boundSql = ms.getBoundSql(parameter);
              cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
           else 
              cacheKey = (CacheKey)args[4];
              boundSql = (BoundSql)args[5];
          
		  // 检查方言是否存在
          this.checkDialectExists();
          if (this.dialect instanceof Chain) 
          	  // 对原始sql语句进行处理
              boundSql = ((Chain)this.dialect).doBoundSql(Type.ORIGINAL, boundSql, cacheKey);
          

          List resultList;
          //调用方法判断是否需要进行分页,如果不需要,直接返回结果
          if (!this.dialect.skip(ms, parameter, rowBounds)) 
         	 //判断是否需要进行 count 查询
              if (this.dialect.beforeCount(ms, parameter, rowBounds)) 
              		//查询总数
                  Long count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql);
                  //处理查询总数,返回 true 时继续分页查询,false 时直接返回
                  if (!this.dialect.afterCount(count, parameter, rowBounds)) 
                  //当查询总数为 0 时,直接返回空的结果
                      Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);
                      return var12;
                  
              
			  // 执行分页
              resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
           else 
              resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
          

          Object var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
          return var16;
       finally 
      	  // 清空ThreadLocal变量
          if (this.dialect != null) 
              this.dialect.afterAll();
          

      
  

进入到ExecutorUtil.pageQuery()方法

public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey) throws SQLException 
   if (!dialect.beforePage(ms, parameter, rowBounds)) 
        return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
     else 
    	// 参数处理,把查询条件转换成Map<String, Object>
    	// 并且会put两个新参数 
    	// First_PageHelper和Second_PageHelper分别是前面的pageNum和PageSize
        parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);
        // sql处理 这里根据分页参数的起始行 添加LIMIT ?, ? 或者 LIMIT ?
        String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);
        BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, bou

以上是关于PageHelper分页原理源码分析的主要内容,如果未能解决你的问题,请参考以下文章

pagehelper怎么计算总数的

吃透Mybatis源码-通过分析Pagehelper源码来理解Mybatis的拦截器

pagehelper分页中pageSize等于total的问题结合源码分析

PageHelper分页合理化reasonable源码分析

PageHelper分页合理化reasonable源码分析

PageHelper分页合理化reasonable源码分析