preface

Mybatis is an excellent semi-automatic ORM framework, which is widely used in China. The enhanced version, plus, uses paging during development for high volumes of data. PageHelper is a very good paging plug-in, we do not need to directly write count SQL, do not need to write limit offset, only need to call a small number of methods can be paging, very convenient, improve the development efficiency. Mybatis Generator, Mybatis Plugin and Mybatis Generator.

use

First we import the Maven dependency

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>4.14.</version>
</dependency>
Copy the code

SpringMVC

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath:mapper/*.xml"/> <property name="plugins"> <array> <bean class="com.github.pagehelper.PageHelper"> <property name="properties"> <! Dialect =mysql </value> </property> </bean> </array> </property> </bean>Copy the code

For SpringBoot, you need to create an SqlSessionFactoryBean by new it, setPlugins, and build it.

Let’s see how to use it first. Okay

PageHelper.startPage(page.getPageNo(),page.getPageSize());
List<CollectionVO> data1 = collectionMapper.selectByUser(userId,page);

PageInfo<CollectionVO> pageInfo1 = new PageInfo<>(data1);
Copy the code

Pagehelper. startPage, PageInfo, PageInfo, PageInfo, PageInfo, PageInfo, PageInfo

The source code parsing

Mybatis SqlSessionFactoryBean plugins Settings com. Making. Pagehelper. Pagehelper

PageHelper implements the Interceptor interface, which is mybatis reserved interface for extension plugins. By implementing this interface, write plug-in logic and set it to plugins, you can execute the content of the plug-in when MyBatis is executing. Let’s continue to look at the following.

So how do you use this plug-in?

PageHelper.startPage(int pageNum, int pageSize)
Copy the code

PageHelper is used to set the current page number and page size before mapper queries where we need to page.

If we go to the startPage method, we can see that we are using the constructor pattern, the predecessor of the Builder pattern

public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
  		  // Create a page object, save the page number, whether statistics, page number, sorting rules, etc
        Page<E> page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        Page<E> oldPage = SqlUtil.getLocalPage();
        if(oldPage ! =null && oldPage.isOrderByOnly()) {
          // Set the collation rules
            page.setOrderBy(oldPage.getOrderBy());
        }
			  // Put the currently instantiated Page object into ThreadLocal, so that the offset and limit of the Page can be calculated at execution time
        SqlUtil.setLocalPage(page);
        return page;
    }
Copy the code

So let’s go in and look at Page, and I’ve posted some code here, and we can see that it’s all about setting up pages, sorting rules, whether or not we count totals, and so on.

public class Page<E> extends ArrayList<E> {
    private static final long serialVersionUID = 1L;
    private int pageNum;
    private int pageSize;
    private int startRow;
    private int endRow;
    private long total;
    private int pages;
    private boolean count;
    private Boolean countSignal;
    private String orderBy;
    private boolean orderByOnly;
    private Boolean reasonable;
    private Boolean pageSizeZero;

    public Page(a) {}... }Copy the code

The corresponding Page is generated after startPage and put into ThreadLocal via sqlutil.setLocalPage (Page), which can then be retrieved via ThreadLocal

Next, call mapper.xxxx()

PageHelper.startPage(page.getPageNo(),page.getPageSize());

List<CollectionVO> data1 = collectionMapper.selectByUser(userId,page);

PageInfo<CollectionVO> pageInfo1 = new PageInfo<>(data1);

Copy the code

Mybatis is executed by Executor, PrepareStatement, PrepareStatement, PrepareStatement, PrepareStatement, PrepareStatement, PrepareStatement

We directly into the theme, into the org. Apache. Ibatis. Session. The Configuration class, see the following method

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    // Three basic actuators
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
  	// Here CacheExecutor is a decorator enhancement to the previous one, decorator mode
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
  	// The plugin is loaded here
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
Copy the code

Then we enter into the org. Apache. Ibatis. Plugin. InterceptorChain method as follows

public Object pluginAll(Object target) {
  	// If you need to ask me where the interceptors collection is initialized, see the following
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
Copy the code

Initialization of interceptors, we can see org. Mybatis. Spring. SqlSessionFactoryBean buildSqlSessionFactory down, since the code too much, here only to intercept, as follows

	  This. Plugins are what we put in SqlSessionFactoryBean to loadBeanDefinition. Injected into the properties of the SqlSessionFactoryBean instance, which may require a bit of Spring source code
    if(! isEmpty(this.plugins)) {
      // Walk through all the plug-ins
      for (Interceptor plugin : this.plugins) {
        // Add to the list
        configuration.addInterceptor(plugin);
        if (LOGGER.isDebugEnabled()) {
          LOGGER.debug("Registered plugin: '" + plugin + "'"); }}}Copy the code

Continue to enter the configuration… addInterceptor (plugins), org. Apache. The ibatis. Session. The configuration method of addInterceptor, The addInterceptor method of the InterceptorChain is called, so the PageHelper plugin is included in the Interceptors. Let’s go back to the main line and keep going.

The above after org. Apache. Ibatis. Plugin. InterceptorChain. After pluginAll method, we get a actuator proxy objects, at the time of SQL execution, will give the executive power to proxy objects.

Here in see pluginAll method, into the com. Making. Pagehelper. Pagehelper method as follows

public Object plugin(Object target) {
  			// The target object is the executor, which generates the proxy
        if (target instanceof Executor) {
            return Plugin.wrap(target, this);
        } else {
            returntarget; }}Copy the code

Into the org. Apache. Ibatis. Plugin. Wrap method of the plugin

public static Object wrap(Object target, Interceptor interceptor) { Map<Class<? >, Set<Method>> signatureMap = getSignatureMap(interceptor);// Target class class objectClass<? > type = target.getClass(); Class<? >[] interfaces = getAllInterfaces(type, signatureMap);if (interfaces.length > 0) {
      // Construct the JDK proxy, where the InvocationHandler is the Plugin object, so that the final executor execution is propired to the Plugin's invoke method.
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }
Copy the code

Then we enter into the org. Apache. Ibatis. Plugin. The plugin. The invoke method, as follows

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if(methods ! =null && methods.contains(method)) {
        // Just look here. Execute the Intercept method of PageHelper and pass the parameters, target object, method, etc
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throwExceptionUtil.unwrapThrowable(e); }}Copy the code

Back to com. Making. Pagehelper) pagehelper) intercept, the final treatment is conducted here.

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

Since we are just looking at the main line, some of the logic will not be discussed, go to the SQlutil. processPage(Invocation), proceed to the _processPage(Invocation), When the Invocation invocation (Page, ARGS) comes in we can see that the associated SQL is handled here. First let’s see how the summation query is done.

 /** * 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)) {// Build a count MapperStatement, because we need such a Statement to query the total number
            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
              	// There is a query to be executed
                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);
                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

After invocation. Proceed () we will enter to org. The apache. The ibatis. Executor. CachingExecutor. Query method, Then we enter getBoundSql (parameterObject) into the org. Apache. Ibatis. Mapping. MappedStatement. GetBoundSql

And then to the com. Making. Pagehelper. Sqlsource. PageSqlSource method as follows

/** * get BoundSql **@param parameterObject
     * @return* /
    @Override
    public BoundSql getBoundSql(Object parameterObject) {
        Boolean count = getCount();
        if (count == null) {
            return getDefaultBoundSql(parameterObject);
        } else if (count) {
            // Generate count SQL, let's go here
            return getCountBoundSql(parameterObject);
        } else {
            returngetPageBoundSql(parameterObject); }}Copy the code

Into getCountBoundSql, continue to point into, we will eventually enter to com. The dead simple. Pagehelper) parser. SqlParser getSmartCountSql method, as follows

public String getSmartCountSql(String sql) {
        // Verify whether the SQL is supported
        isSupportedSql(sql);
  			// If the SQL has been cached, return it directly
        if(CACHE.get(sql) ! =null) {
            return CACHE.get(sql);
        }
        / / parse SQL
        Statement stmt = null;
        try {
            // The parser parses the SQL. Jsqlparser is used here.
            stmt = CCJSqlParserUtil.parse(sql);
        } catch (Throwable e) {
            // Return the count statement in the normal way if it cannot be parsed
            String countSql = getSimpleCountSql(sql);
            CACHE.put(sql, countSql);
            return countSql;
        }
  			// After parsing, you get a Select object, and populate the properties of the Select object with each part of the SQL parsing
        Select select = (Select) stmt;
        SelectBody selectBody = select.getSelectBody();
        // Handle body- go to order by
        processSelectBody(selectBody);
        // handle with- order by
        processWithItemsList(select.getWithItemsList());
        // process the query as count
  			// Add count(0) and alias to the SQL
        sqlToCount(select);
        String result = select.toString();
        CACHE.put(sql, result);
  			Select count(0) from (XXXXXXX) table_alias
        return result;
    }
Copy the code

Back to the com. Making. Pagehelper. SqlUtil. DoProcessPage method, as follows, to get the result that contains a list of the total, take the first set to the total property Page object

if (page.isCount()) {
                page.setCountSignal(Boolean.TRUE);
                / / replace MS
                args[0] = msCountMap.get(ms.getId());
                // Query the total number
              	// There is a query to be executed
                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);
            }
Copy the code

The List result of the query is in the following flow, which is to execute the original SQL, and then get the List result set, which is added to the Page, which is a List, inherited from the ArrayList

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);
                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 result
                page.addAll((List) result);
            }
Copy the code

The result is a Page object with totals and results, which mapper.xxxxx() returns, as shown below. Instead of a List, we return a Page object

At this time we use PageInfo format, get to the front end of the page information, at this point, the whole process is over.