Page Helper is used to paginate with mybatis and Page Interceptor is used to understand the interceptor of mybatis. Mybatis version is 3.4.6, and Mybatis Helper version is 5.1.3.
1,PageInterceptor
Start with the previous code, List-1 as follows:
List-1
@Test public void testPage() { PageHelper.startPage(2, 3); List<Person> all = personMapper.findAll(); PageInfo<Person> personPageInfo = new PageInfo<>(all,3); log.info(all.toString()); int pageNum = personPageInfo.getPageNum(); int pageSize = personPageInfo.getSize(); int pages = personPageInfo.getPages(); log.info("pageNum:"+pageNum+" size:"+ pageSize +" pages:"+ pages); }
List-1 queries all Person s, but paging queries. Note that after using PageHelper, the list type all is com.github.pagehelper.Page, which inherits JDK's Array List. As shown in List-2 below, I initially thought it was JDK's List implementation, until I saw PageInfo, I found out that it was PageHelper's Array inherited. List:
List-2
public class Page<E> extends ArrayList<E> implements Closeable { 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 reasonable; private Boolean pageSizeZero; private String countColumn; private String orderBy; private boolean orderByOnly; ......
mybatis's Interceptor is shown in List-3 below. Page Interceptor implements this interface:
List-3
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }
To see how PageInterceptor implements the intercept interface, List-4 below
List-4
@Override 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; //Because of logical relations, only one entry is allowed. if(args.length == 4){ //Four parameters boundSql = ms.getBoundSql(parameter); cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql); } else { //6 parameters cacheKey = (CacheKey) args[4]; boundSql = (BoundSql) args[5]; } List resultList; //Call the method to determine whether pagination is required or not, and return the result directly if not if (!dialect.skip(ms, parameter, rowBounds)) { //Dynamic parameter acquisition by reflection String msId = ms.getId(); Configuration configuration = ms.getConfiguration(); Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql); //Determine whether count queries are required if (dialect.beforeCount(ms, parameter, rowBounds)) { String countMsId = msId + countSuffix; Long count; //First determine whether there is a handwritten count query MappedStatement countMs = getExistedMappedStatement(configuration, countMsId); if(countMs != null){ count = executeManualCount(executor, countMs, parameter, boundSql, resultHandler); } else { countMs = msCountMap.get(countMsId); //Automatic Creation if (countMs == null) { //Create an ms with a return value of Long type based on the current ms countMs = MSUtils.newCountMappedStatement(ms, countMsId); msCountMap.put(countMsId, countMs); } count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler); } //Total number of queries processed //Continue paging queries when true is returned, and return directly when false is returned if (!dialect.afterCount(count, parameter, rowBounds)) { //When the total number of queries is 0, empty results are returned directly. return dialect.afterPage(new ArrayList(), parameter, rowBounds); } } //Determine whether paging queries are required if (dialect.beforePage(ms, parameter, rowBounds)) { //Generating Paging Cache key CacheKey pageKey = cacheKey; //Processing parameter objects parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey); //Call dialect to get paging sql String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey); BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter); //Setting dynamic parameters for (String key : additionalParameters.keySet()) { pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); } //Perform paging queries resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql); } else { //In the case of no paging, no memory paging is performed resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql); } } else { //rowBounds still supports default memory paging with parameter values and without paging plug-ins resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } return dialect.afterPage(resultList, parameter, rowBounds); } finally { dialect.afterAll(); } }
Let's analyze the content of List-4 and say, "if (! Dialect. skip (ms, parameters, rowBounds){" to determine whether paging is needed. This dialect is PageHelper. List-5 below shows "pageParams. getPage (parameterObject, rows)";
List-5
public class PageHelper extends PageMethod implements Dialect { private PageParams pageParams; private PageAutoDialect autoDialect; @Override public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) { if(ms.getId().endsWith(MSUtils.COUNT)){ throw new RuntimeException("Multiple paging plug-ins have been found in the system. Please check the system configuration.!"); } Page page = pageParams.getPage(parameterObject, rowBounds); if (page == null) { return true; } else { //Set the default count column if(StringUtil.isEmpty(page.getCountColumn())){ page.setCountColumn(pageParams.getCountColumn()); } autoDialect.initDelegateDialect(ms); return false; } } ...
If no paging is required in List-4, the query method of executor is called directly. In the case of paging, the first thing to see is whether a count query is needed -- if (dialect.beforeCount (ms, parameters, rowBounds) in List-4, dialect.beforeCount is finally implemented in the beforeCount method of AbstractHelperDialect, as follows: List-6, getLocalPage() calls PageHelper.getLocalPage() to get com. github. pager. Page -- Onderly returns Orderly by default. Alse, isCount returns true by default.
List-6
public abstract class AbstractHelperDialect extends AbstractDialect implements Constant { ... @Override public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) { Page page = getLocalPage(); return !page.isOrderByOnly() && page.isCount(); } ...
In List-4, after you need count query, you can judge that handwritten count exists or does not exist, then call the builder of mybatis and other tool classes to construct it. After that, you can call dialect's afterCount method to implement the afterCount method in AbstractHelperDialect. List-7, getLocalPage() gets the Page in PageInfo, and then sets total. When paging, we set PageHelper pageSize, so as long as count results are greater than 0, we return true.
List-7
@Override public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) { Page page = getLocalPage(); page.setTotal(count); if (rowBounds instanceof PageRowBounds) { ((PageRowBounds) rowBounds).setTotal(count); } //Paging queries are not performed when pageSize < 0 //When pageSize = 0, follow-up queries need to be executed, but no pagination is required. if (page.getPageSize() < 0) { return false; } return count > 0; }
List-4 queries count, and greater than 0, then continue to follow up, first to determine whether a paging query is needed - List-4 in the "dialect. beforePage (ms, parameters, rowBounds)", the implementation is in AbstractHelper Dialect, this is no longer in depth here, as long as our pageSize settings are greater than 0, the default method is to return true.
Paging queries call the getPageSql method of AbstractHelperDialect to get the SQL executed by the database. Take mysql as an example. AbstractHelperDialect calls the getPageSql method of the subclass MySqlDialect, List-8 below, and adds a limit statement at the end of the sql. There are many subclasses of AbstractHelperDialect that correspond to different databases. Template design patterns are used here. The SQL is then handed over to the executor to perform the paging operation. Some people say that mybatis paging query plug-in bottom is all found after memory segmentation, but what I see is paging through limit, no problem, the console print SQL is also with limit.
List-8
@Override public String getPageSql(String sql, Page page, CacheKey pageKey) { StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14); sqlBuilder.append(sql); if (page.getStartRow() == 0) { sqlBuilder.append(" LIMIT ? "); } else { sqlBuilder.append(" LIMIT ?, ? "); } pageKey.update(page.getPageSize()); return sqlBuilder.toString(); }
In List-4, after the result of the paging query is obtained, the "dialect.afterPage (resultList, parameters, rowBounds)" method of AbstractHelperDialect is called, as follows: List-9, to get the Page from PageHelper, and then the result of the paging query is put into Page.
List-9
@Override public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) { Page page = getLocalPage(); if (page == null) { return pageList; } page.addAll(pageList); if (!page.isCount()) { page.setTotal(-1); } else if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) { page.setTotal(pageList.size()); } else if(page.isOrderByOnly()){ page.setTotal(pageList.size()); } return page; }
The last final block in List-4 calls dialect.afterAll(), so let's look at the implementation, as follows: after All () of PageHelper to clean up the items.
List-10
@Override public void afterAll() { //This method is executed even without pagination, so null is judged AbstractHelperDialect delegate = autoDialect.getDelegate(); if (delegate != null) { delegate.afterAll(); autoDialect.clearDelegate(); } clearPage(); }
Ultimately, the PageInterceptor's intercept method returns its own defined Page.
2,PageHelper
Take a look at PageHelper, its parent class PageMethod, List-10 below, which uses ThreadLocal to store Page. If you are familiar with JDK's ThreadLocal, you should also pay attention to the use of PageInterceptor, which is no longer in depth here.
List-10
public abstract class PageMethod { protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>(); protected static boolean DEFAULT_COUNT = true; /** * Setting Page parameters * * @param page */ protected static void setLocalPage(Page page) { LOCAL_PAGE.set(page); } /** * Get Page parameters * * @return */ public static <T> Page<T> getLocalPage() { return LOCAL_PAGE.get(); } /** * Remove local variables */ public static void clearPage() { LOCAL_PAGE.remove(); } ...
3. Where to call Interceptor
When to call Interceptor? Let's look at some methods of Configuration of mybatis. These methods finally call interceptor Chain, as shown in List-12, using responsibility chain mode, calling plugin method, and finally calling Interceptor's intercept method.
List-11
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) { ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql); parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler); return parameterHandler; } public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql) { ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler); return resultSetHandler; } public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql); statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } public Executor newExecutor(Transaction transaction) { return newExecutor(transaction, defaultExecutorType); } public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; 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); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
List-12
public class InterceptorChain { private final List<Interceptor> interceptors = new ArrayList<Interceptor>(); public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = interceptor.plugin(target); } return target; } public void addInterceptor(Interceptor interceptor) { interceptors.add(interceptor); } public List<Interceptor> getInterceptors() { return Collections.unmodifiableList(interceptors); } }
Think about what else plug-ins can do, we can use sql performance time-consuming statistics, or update operations unified plus updates, time and so on.