Data paging function is a necessary function in our software system, in the case of persistent layer using Mybatis, pageHelper to achieve background paging is a common choice, so this article special class introduction.

PageHelper principle

Related,

<dependency>
	<groupId>org.mybatis</groupId>
	<artifactId>mybatis</artifactId>
	<version>3.28.</version>
</dependency>
<dependency>
	<groupId>com.github.pagehelper</groupId>
	<artifactId>pagehelper</artifactId>
	<version>1.215.</version>
</dependency>
Copy the code

1. Add the plugin

To use PageHelper, first configure it in mybatis global configuration file. As follows:

<? 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> <plugins> <! -- com.github. Pagehelper specifies the package name of the pageHelper class --> <plugin interceptor="com.github.pagehelper.PageHelper">
			<property name="dialect" value="mysql"/ > <! -- The default value isfalse-- > <! - set totrueThe first RowBounds argument, offset, is used as the pageNum page number --> <! -- same as pageNum in startPage --> <property name="offsetAsPageNum" value="true"/ > <! -- The default value isfalse-- > <! - set totrueThe count query is performed using RowBounds paging --> <property name="rowBoundsWithCount" value="true"/ > <! - set totrueIf pageSize=0Or RowBounds. Limit =0All results will be queried --> <! --> <property name="pageSizeZero" value="true"/ > <! --3.3. 0Version available - Paging parameter rationalization, defaultfalseDisabled - > <! -- Enable rationalization if pageNum<1Select * from pageNum>pages; select * from pageNum>pages; -- Disable rationalization if pageNum<1Or pageNum>pages returns null data --> <property name="reasonable" value="false"/ > <! --3.5. 0Version available - in order to support the startPage(Object Params) method -> <! -- Added a 'params' parameter to configure parameter mapping for values from Map or ServletRequest --> <! - you can configure the pageNum, pageSize, count, pageSizeZero, reasonable, not configure mapping with default values -- -- > <! Do not copy this configuration without understanding the meaning --> <property name="params" value="pageNum=start; pageSize=limit;"/ > <! -- always always returns PageInfo, check checks whether the return type is PageInfo, None returns Page --> < Property name="returnPageInfo" value="check" />
		</plugin>
	</plugins>
</configuration>
Copy the code

2. Loading process

Let’s demonstrate this with the following lines of code

// Get the configuration file
InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml");
// Get the SqlSessionFactory object by loading the configuration file
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// Get the SqlSession object
SqlSession session = factory.openSession();
PageHelper.startPage(1.5);
session.selectList("com.bobo.UserMapper.query");
Copy the code

Loading the configuration file let’s start with this line of code

new SqlSessionFactoryBuilder().build(inputStream);
Copy the code
public SqlSessionFactory build(InputStream inputStream) {
   return build(inputStream, null.null);
 }
Copy the code





private void pluginElement(XNode parent) throws Exception {
  if(parent ! =null) {
    for (XNode child : parent.getChildren()) {
      / / access to content: com. Making. Pagehelper. Pagehelper
      String interceptor = child.getStringAttribute("interceptor");
      // Get the configured attribute information
      Properties properties = child.getChildrenAsProperties();
      // Create an interceptor instance
      Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
      // Bind attributes to interceptors
      interceptorInstance.setProperties(properties);
      // This method needs to be checkedconfiguration.addInterceptor(interceptorInstance); }}}Copy the code
public void addInterceptor(Interceptor interceptor) {
  	// Add interceptors to the interceptor chain, which is essentially an ordered collection of lists
    interceptorChain.addInterceptor(interceptor);
  }
Copy the code

Summary: By fetching the SqlSessionFactory object, we load the global configuration file and mapping file and add the configured interceptor to the interceptor chain.

3. Interception information defined by PageHelper

Let’s take a look at the header definition for PageHelper

@SuppressWarnings("rawtypes")
@Intercepts( @Signature( type = Executor.class, method = "query", args = {MappedStatement.class , Object.class , RowBounds.class , ResultHandler.class }))
public class PageHelper implements Interceptor {
    / / SQL tools
    private SqlUtil sqlUtil;
    // Attribute parameter information
    private Properties properties;
    // Set the object mode
    private SqlUtilConfig sqlUtilConfig;
    Dialect automatically obtains dialect, which can work without setProperties or setSqlUtilConfig
    private boolean autoDialect = true;
    // The dialect is automatically obtained at runtime
    private boolean autoRuntimeDialect;
    // In the case of multiple data sources, determine whether to close the data source after obtaining jdbcurl
    private boolean closeConn = true;
Copy the code
// The interceptor object is defined
// query(MappedStatement ms,Object o,RowBounds ob ResultHandler rh)
// This method
type = Executor.class, 
method = "query", 
args = {MappedStatement.class
		, Object.class
		, RowBounds.class
		, ResultHandler.class
	}))
Copy the code

PageHelper defines what methods the interceptor intercepts.

4.Executor

Next we need to analyze what happens to the Executor during the SqlSession instantiation. We need to start tracing from this line of code

SqlSession session = factory.openSession();
Copy the code
public SqlSession openSession(a) {
  return openSessionFromDataSource(configuration.getDefaultExecutorType(), null.false);
}
Copy the code







Strengthen the Executor



At this point we see that the Executor object is actually enhanced by the proxy class we live in. The invoke code is

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
    Set<Method> methods = signatureMap.get(method.getDeclaringClass());
    // Execute the intercept method if it is defined
    if(methods ! =null && methods.contains(method)) {
      // Enter to view the method enhancement
      return interceptor.intercept(new Invocation(target, method, args));
    }
    // Not the method that needs to intercept is executed directly
    return method.invoke(target, args);
  } catch (Exception e) {
    throwExceptionUtil.unwrapThrowable(e); }}Copy the code
/** * Mybatis interceptor method **@paramThe Invocation interceptor entry parameter *@returnReturns the execution result *@throwsThrowable throws an exception */
public Object intercept(Invocation invocation) throws Throwable {
    if (autoRuntimeDialect) {
        SqlUtil sqlUtil = getSqlUtil(invocation);
        return sqlUtil.processPage(invocation);
    } else {
        if (autoDialect) {
            initSqlUtil(invocation);
        }
        returnsqlUtil.processPage(invocation); }}Copy the code

We will examine this method later. Now that we’re done with Executor’s analysis, let’s look at how PageHelper implements paging.

5. Paging process

Next, we’ll look at the paging process through code trace. We’ll start with two lines of code:

5.1 startPage

PageHelper.startPage(1.5);
Copy the code
/** * start paging **@param params
 */
public static <E> Page<E> startPage(Object params) {
    Page<E> page = SqlUtil.getPageFromObject(params);
    // When Order Derby has already been executed
    Page<E> oldPage = SqlUtil.getLocalPage();
    if(oldPage ! =null && oldPage.isOrderByOnly()) {
        page.setOrderBy(oldPage.getOrderBy());
    }
    SqlUtil.setLocalPage(page);
    return page;
}
Copy the code
/** * start paging **@paramPageNum page *@paramPageSize Number of displays per page *@paramCount Whether to perform count query *@paramReasonable pagination is reasonable. When null, the default configuration */ is used
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable) {
    return startPage(pageNum, pageSize, count, reasonable, null);
}
Copy the code
/** * start paging **@paramOffset page *@paramLimit Number of pages displayed *@paramCount Whether to perform count query */
public static <E> Page<E> offsetPage(int offset, int limit, boolean count) {
    Page<E> page = new Page<E>(new int[]{offset, limit}, count);
    // When Order Derby has already been executed
    Page<E> oldPage = SqlUtil.getLocalPage();
    if(oldPage ! =null && oldPage.isOrderByOnly()) {
        page.setOrderBy(oldPage.getOrderBy());
    }
    // This is important!!
    SqlUtil.setLocalPage(page);
    return page;
}
Copy the code
private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
// Save paging information in ThreadLocal, thread-safe!
public static void setLocalPage(Page page) {
    LOCAL_PAGE.set(page);
}
Copy the code

5.2 selectList method

session.selectList("com.bobo.UserMapper.query");
Copy the code
public <E> List<E> selectList(String statement) {
  return this.selectList(statement, null);
}

public <E> List<E> selectList(String statement, Object parameter) {
  return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
Copy the code



We need to go back to the Invoke method and continue

/** * Mybatis interceptor method **@paramThe Invocation interceptor entry parameter *@returnReturns the execution result *@throwsThrowable throws an exception */
public Object intercept(Invocation invocation) throws Throwable {
    if (autoRuntimeDialect) {
        SqlUtil sqlUtil = getSqlUtil(invocation);
        return sqlUtil.processPage(invocation);
    } else {
        if (autoDialect) {
            initSqlUtil(invocation);
        }
        returnsqlUtil.processPage(invocation); }}Copy the code

Enter the sqlUtil. ProcessPage (invocation); methods

/** * Mybatis interceptor method **@paramThe Invocation interceptor entry parameter *@returnReturns the execution result *@throwsThrowable throws an exception */
private Object _processPage(Invocation invocation) throws Throwable {
    final Object[] args = invocation.getArgs();
    Page page = null;
    // When method arguments are supported, the Page is first tried
    if (supportMethodsArguments) {
    	// Get Page information from thread-local variables, which we just set
        page = getPage(args);
    }
    // Paging information
    RowBounds rowBounds = (RowBounds) args[2];
    // If page == null, there is no paging condition and no paging query is required
    if ((supportMethodsArguments && page == null)
            When paging parameters are not supported, determine LocalPage and RowBounds to determine whether paging is required| | (! supportMethodsArguments && SqlUtil.getLocalPage() ==null && rowBounds == RowBounds.DEFAULT)) {
        return invocation.proceed();
    } else {
        // Page ==null when paging parameters are not supported
        if(! supportMethodsArguments && page ==null) {
            page = getPage(args);
        }
        // Enter to view
        returndoProcessPage(invocation, page, args); }}Copy the code
/** * Mybatis interceptor method **@paramThe Invocation interceptor entry parameter *@returnReturns the execution result *@throwsThrowable throws an exception */
 private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {
     // Save RowBounds state
     RowBounds rowBounds = (RowBounds) args[2];
     // Get the original ms
     MappedStatement ms = (MappedStatement) args[0];
     // Determine and process as PageSqlSource
     if(! isPageSqlSource(ms)) { processMappedStatement(ms); }// Set the current parser. The ThreadLocal value is set before each parser is used
     ((PageSqlSource)ms.getSqlSource()).setParser(parser);
     try {
         // Ignore RowBounds- otherwise it will paging Mybatis
         args[2] = RowBounds.DEFAULT;
         // If only sort or pageSizeZero judgment
         if (isQueryOnly(page)) {
             return doQueryOnly(page, invocation);
         }

         // Simply use the value of total to determine whether to run a count query
         if (page.isCount()) {
             page.setCountSignal(Boolean.TRUE);
             / / replace MS
             args[0] = msCountMap.get(ms.getId());
             // Query the total number
             Object result = invocation.proceed();
             / / reduction of ms
             args[0] = ms;
             // Set the total number
             page.setTotal((Integer) ((List) result).get(0));
             if (page.getTotal() == 0) {
                 returnpage; }}else {
             page.setTotal(-1l);
         }
         PageSize >0; pageSize<=0; pageSize<=0
         if (page.getPageSize() > 0 &&
                 ((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0) || rowBounds ! = RowBounds.DEFAULT)) {// Replace the MappedStatement argument with the new QS
             page.setCountSignal(null);
             // The point is to look at the method
             BoundSql boundSql = ms.getBoundSql(args[1]);
             args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
             page.setCountSignal(Boolean.FALSE);
             // Perform paging queries
             Object result = invocation.proceed();
             // Get the processing resultpage.addAll((List) result); }}finally {
         ((PageSqlSource)ms.getSqlSource()).removeParser();
     }

     // Return the result
     return page;
 }
Copy the code

Go to the BoundSql BoundSql = Ms. GetBoundSql (args[1]) method to trace to the PageStaticSqlSource class

@Override
protected BoundSql getPageBoundSql(Object parameterObject) {
    String tempSql = sql;
    String orderBy = PageHelper.getOrderBy();
    if(orderBy ! =null) {
        tempSql = OrderByParser.converToOrderBySql(sql, orderBy);
    }
    tempSql = localParser.get().getPageSql(tempSql);
    return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject);
}
Copy the code

You can also look at Oracle’s implementation of paging

So far we have found that the Implementation of PageHelper pagination turns out to be dynamically splicing SQL statements together with pagination statements before we execute SQL statements, thus achieving pagination fetched from the database.

Concern public number: Java baodian