透过PageHelper看Mybatis插件机制

一、startPage做了什么

PageHelper.startPage(1,20);

这是PageHelper推荐的使用方式,后续的第一个查询方法会分页。
观察这个方法的内部逻辑
public static Page startPage(int pageNum, int pageSize, boolean count) { // 封装成page对象 Page page = new Page(pageNum, pageSize, count); // == 将page对象存在`PageMethod#LOCAL_PAGE`——这是一个ThreadLocal setLocalPage(page); return page; }

二、PageHelper如何与Mybatis建立联系的? 【透过PageHelper看Mybatis插件机制】正是通过mybatis的插件机制plugin,入口在executor创建部分。
org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession() org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType){ // == 过滤器链对executor做封装 executor = (Executor) interceptorChain.pluginAll(executor); }

观察InterceptorChain interceptorChain结构:
class InterceptorChain { // ## 1.拦截器 private final List interceptors = new ArrayList<>(); public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { // ## 2.包装 target = interceptor.plugin(target); } return target; }

1.拦截器 被@Intercepts注解修饰的类就是拦截器,也就是所谓的插件。
以PageInterceptor为例:
@Intercepts( { // 通过@Signature指定被拦截的方法 @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}), } ) public class PageInterceptor implements Interceptor

2.包装 查看具体的包装方法
default Object plugin(Object target) { return Plugin.wrap(target, this); ?????? // == 核心逻辑是动态代理 return Proxy.newProxyInstance( type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)); }

观察invokcationHandler(Plugin)的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 执行拦截器的拦截方法 return interceptor.intercept(new Invocation(target, method, args)); }

观察PageInterceptor#intercept实现:
com.github.pagehelper.PageInterceptor#intercept{ // == 判断是否需要进行分页,如果不需要,直接返回结果 if (!dialect.skip(ms, parameter, rowBounds)) { // -- 判断是否需要进行分页查询,page.getPageSize>0 if (dialect.beforePage(ms, parameter, rowBounds)) { // -- 调用方言获取分页sql,添加limit语句 String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey); //执行分页查询 resultList = executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql); } } }

  • 怎么判断是否需要分页?
com.github.pagehelper.PageHelper#skip public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) { // == PageMethod#LOCAL_PAGE中是否有值 Page page = pageParams.getPage(parameterObject, rowBounds); // -- PageMethod#LOCAL_PAGE有值情况,初始化方言 autoDialect.initDelegateDialect(ms); return false; }

这里就和第一节连上了。
PageHelper.startPage向ThreadLocal中存放了page对象
此处skip()方法判断:如果有值返回false,需分页同时初始化方言dialect;无值返回true,不必分页。
  • 方言dialect的初始化逻辑
com.github.pagehelper.page.PageAutoDialect#initDelegateDialect{ this.delegate = getDialect(ms); } com.github.pagehelper.page.PageAutoDialect#getDialect{ // 通过数据库链接截取别名 String dialectStr = fromJdbcUrl(url); // -- 通过别名初始化 AbstractHelperDialect dialect = initDialect(dialectStr, properties); }com.github.pagehelper.page.PageAutoDialect#initDialect{ // 解析方言的class Class sqlDialectClass = resloveDialectClass(dialectClass); ????? // == 通过dialectAliasMap获取 dialectAliasMap.get(className.toLowerCase()); // 通过反射创建 dialect = (AbstractHelperDialect) sqlDialectClass.newInstance(); }

我们看看dialectAliasMap中存了什么
com.github.pagehelper.page.PageAutoDialect#dialectAliasMap static { //初始化时注册别名 dialectAliasMap.put("hsqldb", HsqldbDialect.class); dialectAliasMap.put("h2", HsqldbDialect.class); dialectAliasMap.put("postgresql", HsqldbDialect.class); dialectAliasMap.put("mysql", MySqlDialect.class); }

mybatis预留扩展
// 参数处理 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; } // Statement处理 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; }

上述方法都有interceptorChain.pluginAll的影子,我们可以自己写拦截器对ParameterHandler、ResultSetHandler、StatementHandler的关键方法进行拦截,官网有详细介绍。

    推荐阅读