使用mybatis时用PageHelper进行分页,用到了PageInterceptor,借此了解下mybatis的interceptor。Mybatis的版本是3.4.6,MybatisHelper的版本是5.1.3。java
先上一段代码,以下List-1:mysql
List-1git
@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中,查询全部的Person,不过度页查询,注意使用PageHelper后,获得的List类型的all是com.github.pagehelper.Page,它继承了JDK的ArrayList,以下List-2所示,我在使用时一开始也觉得是JDK的List实现,直到在看PageInfo时出现一些状况,深刻了解后才发现是PageHelper继承的ArrayList:github
List-2sql
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的Interceptor以下List-3所示,PageInterceptor实现了这个接口:数据库
List-3设计模式
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }
来看PageInterceptor如何实现intercept接口的,以下List-4缓存
List-4mybatis
@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; //因为逻辑关系,只会进入一次 if(args.length == 4){ //4 个参数时 boundSql = ms.getBoundSql(parameter); cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql); } else { //6 个参数时 cacheKey = (CacheKey) args[4]; boundSql = (BoundSql) args[5]; } List resultList; //调用方法判断是否须要进行分页,若是不须要,直接返回结果 if (!dialect.skip(ms, parameter, rowBounds)) { //反射获取动态参数 String msId = ms.getId(); Configuration configuration = ms.getConfiguration(); Map<String, Object> additionalParameters = (Map<String, Object>) additionalParametersField.get(boundSql); //判断是否须要进行 count 查询 if (dialect.beforeCount(ms, parameter, rowBounds)) { String countMsId = msId + countSuffix; Long count; //先判断是否存在手写的 count 查询 MappedStatement countMs = getExistedMappedStatement(configuration, countMsId); if(countMs != null){ count = executeManualCount(executor, countMs, parameter, boundSql, resultHandler); } else { countMs = msCountMap.get(countMsId); //自动建立 if (countMs == null) { //根据当前的 ms 建立一个返回值为 Long 类型的 ms countMs = MSUtils.newCountMappedStatement(ms, countMsId); msCountMap.put(countMsId, countMs); } count = executeAutoCount(executor, countMs, parameter, boundSql, rowBounds, resultHandler); } //处理查询总数 //返回 true 时继续分页查询,false 时直接返回 if (!dialect.afterCount(count, parameter, rowBounds)) { //当查询总数为 0 时,直接返回空的结果 return dialect.afterPage(new ArrayList(), parameter, rowBounds); } } //判断是否须要进行分页查询 if (dialect.beforePage(ms, parameter, rowBounds)) { //生成分页的缓存 key CacheKey pageKey = cacheKey; //处理参数对象 parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey); //调用方言获取分页 sql String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey); BoundSql pageBoundSql = new BoundSql(configuration, pageSql, boundSql.getParameterMappings(), parameter); //设置动态参数 for (String key : additionalParameters.keySet()) { pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); } //执行分页查询 resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql); } else { //不执行分页的状况下,也不执行内存分页 resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql); } } else { //rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页 resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } return dialect.afterPage(resultList, parameter, rowBounds); } finally { dialect.afterAll(); } }
咱们来分析下List-4的内容,"if (!dialect.skip(ms, parameter, rowBounds)) {"判断是否须要进行分页,这个dialect就是PageHelper,以下List-5,有兴趣能够看下"pageParams.getPage(parameterObject, rowBounds);",app
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("在系统中发现了多个分页插件,请检查系统配置!"); } Page page = pageParams.getPage(parameterObject, rowBounds); if (page == null) { return true; } else { //设置默认的 count 列 if(StringUtil.isEmpty(page.getCountColumn())){ page.setCountColumn(pageParams.getCountColumn()); } autoDialect.initDelegateDialect(ms); return false; } } ...
List-4中若是不须要分页,则直接调用executor的query方法。须要分页状况下,首先会看是否须要进行count查询——List-4中的"if (dialect.beforeCount(ms, parameter, rowBounds))",dialect.beforeCount的最终实现是在AbstractHelperDialect的beforeCount方法,以下List-6,getLocalPage()调用PageHelper.getLocalPage(),获得com.github.pagehelper.Page——isOrderByOnly默认返还false,isCount默认返还true。
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(); } ...
List-4中,须要count查询后,判断手写的count已存在,不存在则调用mybatis的builder等工具类构造,以后进行count查询,获得结果后调用dialect的afterCount方法,实如今AbstractHelperDialect的afterCount方法,以下List-7,getLocalPage()获取PageInfo中的Page,以后设置total。在分页的时候,咱们会对PageHelper进行pageSize的设置,因此只要count的结果大于0,就会返还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); } //pageSize < 0 的时候,不执行分页查询 //pageSize = 0 的时候,还须要执行后续查询,可是不会分页 if (page.getPageSize() < 0) { return false; } return count > 0; }
List-4中查询到count,且大于0,则继续后续,先判断是否须要进行分页查询——List-4中的"dialect.beforePage(ms, parameter, rowBounds)",实现是在AbstractHelperDialect中,这里再也不深刻这点,只要咱们的pageSize设置大于0,该方法默认是返还true的。
进行分页查询,会调用AbstractHelperDialect的getPageSql方法获取数据库执行的sql,以mysql为例子,AbstractHelperDialect在调用子类MySqlDialect的getPageSql方法,以下List-8,会在sql的最后加上limit语句。AbstractHelperDialect的子类有不少种,对应不一样的数据库,这里使用了模板设计模式。以后再将这个sql交给executor执行,达到分页的操做。有些人说mybatis的分页查询插件底层上是所有查出到内存以后进行切分,可是我看到的是经过limit进行分页,没有什么问题的,控制台打印的sql也是带有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(); }
List-4中,分页查询到结果后,调用"dialect.afterPage(resultList, parameter, rowBounds)",即AbstractHelperDialect的afterPage方法,以下List-9,从PageHelper中获得Page,以后将分页查询的结果放入到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; }
List-4中最后的finally块调用dialect.afterAll(),咱们来看下实现,以下的PageHelper的afterAll(),清理各项清理。
List-10
@Override public void afterAll() { //这个方法即便不分页也会被执行,因此要判断 null AbstractHelperDialect delegate = autoDialect.getDelegate(); if (delegate != null) { delegate.afterAll(); autoDialect.clearDelegate(); } clearPage(); }
最终,这个PageInterceptor的intercept方法返还的是本身定义的Page。
来看看PageHelper,它的父类PageMethod,以下的List-10,使用ThreadLocal来存储Page,若是熟悉JDK的ThreadLocal那么,对其要注意的点,在PageInterceptor的使用时也要注意,这里再也不深究。
List-10
public abstract class PageMethod { protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>(); protected static boolean DEFAULT_COUNT = true; /** * 设置 Page 参数 * * @param page */ protected static void setLocalPage(Page page) { LOCAL_PAGE.set(page); } /** * 获取 Page 参数 * * @return */ public static <T> Page<T> getLocalPage() { return LOCAL_PAGE.get(); } /** * 移除本地变量 */ public static void clearPage() { LOCAL_PAGE.remove(); } ...
何时调用Interceptor呢,来看mybatis的Configuration的几个方法,这几个方法最后都调用了interceptorChain,如List-12所示,使用责任链模式,调用plugin方法,最后调用Interceptor的intercept方法。
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); } }
思考,插件还能够作其它什么呢,咱们能够用来进行sql性能耗时的统计,或者对更新操做统一的加上更新者、时间等。