Mybatis Interceptor

Keywords: Programming SQL Mybatis JDK github

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.

Reference

  1. Source code
  2. https://blog.csdn.net/top_code/article/details/55657776

Posted by RunningUtes on Sat, 15 Jun 2019 13:31:05 -0700