Take a look at the Page entity class definition for a few important attribute values.

public class Page<E> extends ArrayList<E> {
    private static final long serialVersionUID = 1L;

    /** * no count query */
    private static final int NO_SQL_COUNT = -1;
    /** * count */
    private static final int SQL_COUNT = 0;
    /** * page number, starting from 1 */
    private int pageNum;
    /** * page size */
    private int pageSize;
    /** * start line */
    private int startRow;
    /** ** last line */
    private int endRow;
    /** ** total */
    private long total;
    /** * Total pages */
    private int pages;
    /** ** ** */
    private Boolean reasonable;
    /** * When set to true, if pagesize is set to 0 (or RowBounds limit=0), no paging is performed and all results are returned */
    private Boolean pageSizeZero;
 }
Copy the code

Intercepts is the mybatis annotation, @signature is the method Signature interceptor Intercepts,

Class, Object. Class, RowBounds. Class, resulthandler.class

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

The implementation of Interceptor is to implement mybatis Interceptor interface

public class PageHelper implements Interceptor {
    / / SQL tools
    private SqlUtil sqlUtil;
    // Attribute parameter information
    private Properties properties;
    // Automatically obtain the dialect
    private Boolean autoDialect;
 }
Copy the code

PageHelper’s pagination method is straightforward and unnecessary.

  /** * start paging **@paramPageNum page *@paramPageSize Number of displays per page */
    public static Page startPage(int pageNum, int pageSize) {
        return startPage(pageNum, pageSize, true);
    }

    /** * start paging **@paramPageNum page *@paramPageSize Number of displays per page *@paramCount Whether to perform count query */
    public static Page startPage(int pageNum, int pageSize, boolean count) {
        return startPage(pageNum, pageSize, count, null);
    }

    /** * start paging **@paramPageNum page *@paramPageSize Number of displays per page *@paramOrderBy sorting * /
    public static Page startPage(int pageNum, int pageSize, String orderBy) {
        orderBy(orderBy);
        return startPage(pageNum, pageSize);
    }

    /** * 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 Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable) {
        return startPage(pageNum, pageSize, count, reasonable, null);
    }

    /** * start paging **@paramPageNum page *@paramPageSize Number of displays per page *@paramCount Whether to perform count query *@paramReasonable pagination is reasonable. If null, default configuration * is used@paramPageSizeZero true and pageSize=0 returns all results, false returns pages, and null uses the default configuration */
    public static Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
        Page page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        SqlUtil.setLocalPage(page);
        return page;
    }

    /** * start paging **@param params
     */
    public static Page startPage(Object params) {
        Page page = SqlUtil.getPageFromObject(params);
        SqlUtil.setLocalPage(page);
        return page;
    }

    /** * sort **@param orderBy
     */
    public static void orderBy(String orderBy) {
        OrderByHelper.orderBy(orderBy);
    }
Copy the code

The Interceptor from Mybatis is intercepted from the InnvocationHandler object in the JDK’s Invocation Invocation (Invocation.proceed());

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

It involves sqlUtil, with the Invocation. Proceed () wrapped in a layer. Let’s start with the initialization of sqlUtil.

In the following code, an important class MetaObject is provided by Mybatis for convenient and elegant access to object attributes, through which can simplify the code, do not need to try/catch various reflect exceptions. It also supports operations on javabeans, Collection, and Map objects.

MetaObject is used to obtain the DataSource attribute referenced by the MappedStatement object. The object reference chain is MappedStatement >Configuration >Environment > DataSource

After retrieving the datasource URL, encapsulate it in SqlUtil, and the initialization process is complete.

    /** * initialize sqlUtil **@param invocation
     */
    public synchronized void initSqlUtil(Invocation invocation) {
        if (sqlUtil == null) {
            String url = null;
            try {
                MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
                MetaObject msObject = SystemMetaObject.forObject(ms);
                DataSource dataSource = (DataSource) msObject.getValue("configuration.environment.dataSource");
                url = dataSource.getConnection().getMetaData().getURL();
            } catch (SQLException e) {
                throw new RuntimeException("Paging plug-in initialization exception :" + e.getMessage());
            }
            if (url == null || url.length() == 0) {
                throw new RuntimeException("Unable to automatically obtain jdbcUrl, please configure the dialect parameter in the paging plug-in!");
            }
            String dialect = Dialect.fromJdbcUrl(url);
            if (dialect == null) {
                throw new RuntimeException("The database type cannot be automatically obtained. Please use the dialect parameter!");
            }
            sqlUtil = new SqlUtil(dialect);
            sqlUtil.setProperties(properties);
            properties = null;
            autoDialect = false; }}Copy the code

The _processPage method is used after a look at a few member variables of the sqlUtil class, where the Page object and COUNT are stored in ThreadLocal to avoid concurrency problems.

 private static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
 private static final ThreadLocal<Boolean> COUNT = new ThreadLocal<Boolean>() {
        @Override
        protected Boolean initialValue(a) {
            return null; }};private static final Map<String, MappedStatement> msCountMap = new ConcurrentHashMap<String, MappedStatement>();
Copy the code

Back to see sqlUtil. ProcessPage (invocation) method calls, the final call is com. Making. Pagehelper. SqlUtil. _processPage method. The source code is too long, and the source code has Chinese annotations more intuitive, here just comb through the code logic, pseudo-code is as follows:

 private Object _processPage(Invocation invocation) throws Throwable {
        final Object[] args = invocation.getArgs();
        RowBounds rowBounds = (RowBounds) args[2];
        
        // Branch 1: If there is no page object LocalPage,RowBounds takes the default and does not perform paging
        if (SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT) {
            // If there is a sort requirement, sort interceptor is executed
            if(OrderByHelper.getOrderBy() ! =null) {
                OrderByHelper.processIntercept(invocation);
            }
            // Execute the original SQL without sorting
            return invocation.proceed();
        // Branch 2: paging is required
        } else {
            // Get the original ms
            MappedStatement ms = (MappedStatement) args[0];
            // Determine and process as PageSqlSource
            if(! isPageSqlSource(ms)) { processMappedStatement(ms, parser); }// Ignore RowBounds- otherwise it will paging Mybatis
            args[2] = RowBounds.DEFAULT;
            // Paging information
            Page page = getPage(rowBounds);
            // Branch 2.1 to determine pageSizeZero, there is no need to perform paging
            / / pageSizeZero judgment
            if((page.getPageSizeZero() ! =null && page.getPageSizeZero()) && page.getPageSize() == 0(Object result = Invocation. Proceed ();return page;
            }
            / / branch 2.2
            // Simply use the value of total to determine whether to run a count query
            if (page.isCount()) {
                COUNT.set(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; }}/ / branch 2.3
            PageSize >0; pageSize<=0; pageSize<=0
            if (page.getPageSize() > 0 &&
                    ((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0) || rowBounds ! = RowBounds.DEFAULT)) {// Replace SQL with paging SQL
                BoundSql boundSql = ms.getBoundSql(args[1]);
                args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
                COUNT.set(Boolean.FALSE);
                // Perform paging queries
                Object result = invocation.proceed();
                // Get the processing result
                page.addAll((List) result);
            }
            // Return the result
            returnpage; }}Copy the code

To summarize, branch 1 does not perform paging and has no page-related variables in ThreadLocal at all. SQL > select count, mappedStatement, count from ThreadLocal, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement, mappedStatement Invocation. Proceed () Executes the original SQL; SQL returns paging query results. Paging or not, you end up with a Page object.

The last two PageHelper methods are also interface methods that rewrite the Interceptor.

The plugin() method is the method provided by Mybatis to generate proxy objects. See plugin.wrap (target, this) for details.

  /** * only Executor ** is intercepted@param target
     * @return* /
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            returntarget; }}Copy the code

SetProperties () reads property values from mybatis-config.xml. The following XML example reads the value value of the name attribute.

<plugins>
    <plugin interceptor="com.example.springbootdemo.mybatis.interceptor.MybatisInterceptor">
            <property name="name" value="test"></property>
    </plugin>
</plugins>
Copy the code

Set the database type as follows:

<! After 4.0.0, this parameter can not be set -->
<property name="dialect" value="mysql" />
Copy the code
Public void setProperties(Properties p) {//MyBatis3.2.0 check try { Class.forName("org.apache.ibatis.scripting.xmltags.SqlNode"); } catch (ClassNotFoundException e) {throw new RuntimeException(" MyBatis is too late, MyBatis pagination plugin PageHelper supports MyBatis3.2.0 and above! ); } // Database dialect String dialect = p.getProperty("dialect"); if (dialect == null || dialect.length() == 0) { autoDialect = true; this.properties = p; } else { autoDialect = false; sqlUtil = new SqlUtil(dialect); sqlUtil.setProperties(p); }}}Copy the code