分享免费的编程资源和教程

网站首页 > 技术教程 正文

pageHepler的使用和原理(pagehelper 原理)

goqiw 2025-03-30 16:29:21 技术教程 15 ℃ 0 评论

先说一下pageHepler的简单使用:

//设置当前页和每页大小 
PageHelper.startPage(Integer.parseInt(jobListParam.getCurrentPage()), Integer.parseInt(jobListParam.getPageSize())); 
//在startPage后的第一个查询语句将会被分页 
List jobs=new ArrayList<>(); 
if (CommonUtil.isNotNullList(jobNums)) { 
    jobs = iJobDao.selectByJobNumList(jobNums); 
    logger.info("jobs instanceof Page:{}", jobs instanceof Page); 
  	logger.info("查询结果:{}", JSON.toJSONString(jobs)); 
} //PageInfo返回的就是分页后的数据 PageInfo page=new PageInfo<>(jobs)

xml配置:

  

pageHepler的使用相当的简单,我们接下来来看一下原理:

pageHepler的实现是基于Mybatis对插件的支持(实际是拦截器Interceptor)

在 Mybaits 中允许用插件来拦截的方法包括:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)--封装了统一的增删改查,对外暴露增删改查
  • StatementHandler (prepare, parameterize, batch, update, query)--封装了sql的具体执行步骤,例如prepare(准备语句)、parameterize(参数化)、query(查询)等
  • ParameterHandler (getParameterObject, setParameters)--在StatementHandler的参数化阶段给PreparedStatement设置参数的
  • ResultSetHandler (handleResultSets, handleOutputParameters)--在StatementHandler执行阶段(例如query)处理结果

而pageHepler是拦截的Executor阶段,通过修改

先说PageHelper.startPage()

/**
     * 开始分页
     *
     * @param pageNum      页码
     * @param pageSize     每页显示数量
     * @param count        是否进行count查询
     * @param reasonable   分页合理化,null时用默认配置
     * @param pageSizeZero true且pageSize=0时返回全部结果,false时分页,null时用默认配置
     */
    public static  Page startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
        //这里创建了一个Page,而Page继承了ArrayList,实际上Page就是一个集合
        Page page = new Page(pageNum, pageSize, count);
        page.setReasonable(reasonable);
        page.setPageSizeZero(pageSizeZero);
        //当已经执行过orderBy的时候
        Page oldPage = SqlUtil.getLocalPage();
        if (oldPage != null && oldPage.isOrderByOnly()) {
            page.setOrderBy(oldPage.getOrderBy());
        }
        //这里是将page放到一个ThreadLocal中,以便使用的时候直接get
        //private static final ThreadLocal LOCAL_PAGE = new ThreadLocal();
        SqlUtil.setLocalPage(page);
        return page;
    }

接下来看PageHelper如何执行

首先PageHelper实现了Mybatis的Interceptor接口,我们来看一下Mybatis的Interceptor接口在什么时候执行(这里只研究Executor)

当时用
sqlSessionFactory.openSession()的时候,会创建执行器:final Executor executor = configuration.newExecutor(tx, execType);

在创建执行器的过程中会查询所有的插件,于是PageHelper开始执行

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
  executorType = executorType == null ? defaultExecutorType : executorType;
  //这句再做一下保护,囧,防止粗心大意的人将defaultExecutorType设成null?
  executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
  Executor executor;
  //然后就是简单的3个分支,产生3种执行器BatchExecutor/ReuseExecutor/SimpleExecutor
  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);
  }
  //如果要求缓存,生成另一种CachingExecutor(默认就是有缓存),装饰者模式,所以默认都是返回CachingExecutor
  if (cacheEnabled) {
    executor = new CachingExecutor(executor);
  }
  //此处调用插件,通过插件可以改变Executor行为
  executor = (Executor) interceptorChain.pluginAll(executor);
  return executor;
}
//这里可以看到是调用了plugin方法,然后返回了一个object,并在上面的代码中强制转化为一个Executor,通过这里我们猜测一下,这个object极有可能是一个代理类
//我们接下来代码中看PageHelper的plugin方法
public Object pluginAll(Object target) {
  //循环调用每个Interceptor.plugin方法
  for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
  }
  return target;
}
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
public class PageHelper implements Interceptor {
    //sql工具类
    private SqlUtil sqlUtil;
    //属性参数信息
    private Properties properties;
    //配置对象方式
    private SqlUtilConfig sqlUtilConfig;
    //自动获取dialect,如果没有setProperties或setSqlUtilConfig,也可以正常进行
    private boolean autoDialect = true;
    //运行时自动获取dialect
    private boolean autoRuntimeDialect;
    //多数据源时,获取jdbcurl后是否关闭数据源
    private boolean closeConn = true;
    //缓存
    private Map urlSqlUtilMap = new ConcurrentHashMap();

    /**
     * 获取任意查询方法的count总数
     *
     * @param select
     * @return
     */
    public static long count(ISelect select) {
        Page page = startPage(1, -1, true);
        select.doSelect();
        return page.getTotal();
    }

    /**
     * Mybatis拦截器方法
     *
     * @param invocation 拦截器入参
     * @return 返回执行结果
     * @throws Throwable 抛出异常
     */
     //我们这里知道是Plugin的invoke方法执行然后调用到了这里
     //Invocation类,参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}。因为只有这个参数才会进到这个方法中
    public Object intercept(Invocation invocation) throws Throwable {
        //默认是false
        if (autoRuntimeDialect) {
            //获取对应的分页解析器
            //具体下面有解释
            SqlUtil sqlUtil = getSqlUtil(invocation);
            return sqlUtil.processPage(invocation);
        } else {
            //这里默认是true
            if (autoDialect) {
                initSqlUtil(invocation);
            }
            return sqlUtil.processPage(invocation);
        }
    }

    /**
     * 初始化sqlUtil
     *
     * @param invocation
     */
    public synchronized void initSqlUtil(Invocation invocation) {
        if (this.sqlUtil == null) {
            this.sqlUtil = getSqlUtil(invocation);
            //默认是false
            if (!autoRuntimeDialect) {
                //属性参数信息
                properties = null;
                //配置对象方式
                sqlUtilConfig = null;
            }
            autoDialect = false;
        }
    }

    /**
     * 获取url
     *
     * @param dataSource
     * @return
     */
    public String getUrl(DataSource dataSource){
        Connection conn = null;
        try {
            conn = dataSource.getConnection();
            return conn.getMetaData().getURL();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        } finally {
            if(conn != null){
                try {
                    if(closeConn){
                        conn.close();
                    }
                } catch (SQLException e) {
                    //ignore
                }
            }
        }
    }

    /**
     * 根据daatsource创建对应的sqlUtil
     *
     * @param invocation
     */
     //参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    public SqlUtil getSqlUtil(Invocation invocation) {
        //不要问这里为啥就是MappedStatement,上面已经说过了,指定的
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        //改为对dataSource做缓存,这里是获取数据源
        DataSource dataSource = ms.getConfiguration().getEnvironment().getDataSource();
        //获取链接,没啥意思,这里不用看了
        String url = getUrl(dataSource);
        //默认是空的,首次肯定进不来
        if (urlSqlUtilMap.containsKey(url)) {
            return urlSqlUtilMap.get(url);
        }
        //防止并发
        ReentrantLock lock = new ReentrantLock();
        try {
            lock.lock();
            if (urlSqlUtilMap.containsKey(url)) {
                return urlSqlUtilMap.get(url);
            }
            if (StringUtil.isEmpty(url)) {
                throw new RuntimeException("无法自动获取jdbcUrl,请在分页插件中配置dialect参数!");
            }
            //获取对应的数据库类型枚举
            String dialect = Dialect.fromJdbcUrl(url);
            if (dialect == null) {
                throw new RuntimeException("无法自动获取数据库类型,请通过dialect参数指定!");
            }
            //创建SqlUtil,设置了mysql的分页解析器(其实就是加了 limit ?,?)
            SqlUtil sqlUtil = new SqlUtil(dialect);
            if (this.properties != null) {
                sqlUtil.setProperties(properties);
            } else if (this.sqlUtilConfig != null) {
                sqlUtil.setSqlUtilConfig(this.sqlUtilConfig);
            }
            //设置数据库链接(因为是唯一的),对应的工具类
            urlSqlUtilMap.put(url, sqlUtil);
            return sqlUtil;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 只拦截Executor,这里是mybatis调用plugin方法的地方,由上面猜测,传入的参数是CachingExecutor,这里返回的应该是一个代理类
     * 我们接下来来看Plugin.wrap()方法,再下一个代码块里
     *
     * @param target
     * @return
     */
    public Object plugin(Object target) {
        if (target instanceof Executor) {
            //target=CachingExecutor
            //this=PageHelper
            return Plugin.wrap(target, this);
        } else {
            return target;
        }
    }
}
//这里是Plugin,可以看到它实现了InvocationHandler接口,重写了invoke方法
public class Plugin implements InvocationHandler {

  //这里就是CachingExecutor
  private final Object target;
  //这里的就是PageHelper
  private final Interceptor interceptor;
  //
  private final Map<Class, Set> signatureMap;

  private Plugin(Object target, Interceptor interceptor, Map<Class, Set> signatureMap) {
    this.target = target;
    this.interceptor = interceptor;
    this.signatureMap = signatureMap;
  }

//上面代码块中的plugin方法中调用的就是这里
//target=CachingExecutor,interceptor=PageHelper
  public static Object wrap(Object target, Interceptor interceptor) {
      //这个方法下面有解释,最终返回的是
      //
    Map<Class, Set> signatureMap = getSignatureMap(interceptor);
    //获取CachingExecutor.class
    Class type = target.getClass();
    //type=CachingExecutor.class,signatureMap=
    //这里返回的是Executor.class接口
    Class[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
        //通过这里我们可以看到创建了代理对象并返回,然后我们在mybatis中拿到的Executor就是这里的代理对象,而Executor执行任何方法时都会到我们的invoke方法中方法中(动态代理原理)
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

    //Executor执行任何方法时都会到这里
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
        //这里的method可能是Executor中的任何方法,method.getDeclaringClass()是Executor.class
      Set methods = signatureMap.get(method.getDeclaringClass());
      //上面的代码中我们知道methods中只有Executor中指定的query方法,因此只有PageHelper指定的那个方法才会进入到这个判断中
      if (methods != null && methods.contains(method)) {
          //这里开始调用PageHelper的intercept方法
          //并创建了一个Invocation类,参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}。因为只有这个参数才会进到这个方法中
          //@Intercepts注解中指定了,Invocation下面的代码块中有解释
          //然后我们再回到PageHelper的intercept方法
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

//这里是通过反射获取PageHelper上的注解
//@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
  private static Map<Class, Set> getSignatureMap(Interceptor interceptor) {
      //拿到@Intercepts注解
    Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
    // issue #251
    if (interceptsAnnotation == null) {
      throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());      
    }
    //获取注解中的值:@Signature[]
    Signature[] sigs = interceptsAnnotation.value();
    //这里存放的是key是Executor.class,methods存放的是Executor中指定的query方法
    Map<Class, Set> signatureMap = new HashMap<Class, Set>();
    for (Signature sig : sigs) {
      Set methods = signatureMap.get(sig.type());
      if (methods == null) {
        methods = new HashSet();
        //这的key是Executor.class,methods目前是空
        signatureMap.put(sig.type(), methods);
      }
      try {
          //sig.method()=query
          //sig.args()={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
          //这里是获取Executor中的的query方法,并且参数是固定的
          //通过这个方法可以拿到Executor接口中的:
          // List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
          //不知道的去看一下Executor接口中的方法
        Method method = sig.type().getMethod(sig.method(), sig.args());
        methods.add(method);
      } catch (NoSuchMethodException e) {
        throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
      }
    }
    return signatureMap;
  }

//这里入参是:type=CachingExecutor.class,signatureMap=
  private static Class[] getAllInterfaces(Class type, Map<Class, Set> signatureMap) {
    Set<Class> interfaces = new HashSet<Class>();
    while (type != null) {
        //获取CachingExecutor.class接口Executor.class
      for (Class c : type.getInterfaces()) {
          //判断是否含有Executor.class,我们知道是有的
        if (signatureMap.containsKey(c)) {
            //将Executor.class添加到set中
          interfaces.add(c);
        }
      }
      //将Executor.class从新指给type
      type = type.getSuperclass();
    }
    return interfaces.toArray(new Class[interfaces.size()]);
  }

}
public class Invocation {

  //target=CachingExecutor
  private final Object target;
  //method=Executor中指定的query方法方法
  private final Method method;
  //args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    this.target = target;
    this.method = method;
    this.args = args;
  }

  public Object getTarget() {
    return target;
  }

  public Method getMethod() {
    return method;
  }

  public Object[] getArgs() {
    return args;
  }

  public Object proceed() throws InvocationTargetException, IllegalAccessException {
      //通过反射执行Executor中指定的query方法方法中的执行指定参数
    return method.invoke(target, args);
  }
public class SqlUtil implements Constant {
    private static final ThreadLocal LOCAL_PAGE = new ThreadLocal();
    //params参数映射
    private static Map PARAMS = new HashMap(5);
    //request获取方法
    private static Boolean hasRequest;
    private static Class requestClass;
    private static Method getParameterMap;
    //缓存count查询的ms
    private static final Map msCountMap = new ConcurrentHashMap();
    //RowBounds参数offset作为PageNum使用 - 默认不使用
    private boolean offsetAsPageNum = false;
    //RowBounds是否进行count查询 - 默认不查询
    private boolean rowBoundsWithCount = false;
    //当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
    private boolean pageSizeZero = false;
    //分页合理化
    private boolean reasonable = false;
    //具体针对数据库的parser
    //MysqlParser
    private Parser parser;
    //是否支持接口参数来传递分页参数,默认false
    private boolean supportMethodsArguments = false;
    /**
     * 构造方法
     *
     * @param strDialect
     */
    public SqlUtil(String strDialect) {
        Exception exception = null;
        try {
            //通过名字获取对应的数据库枚举
            Dialect dialect = Dialect.of(strDialect);
            //获取的就是MysqlParser,因为我是用的就是mysql
            parser = AbstractParser.newParser(dialect);
        } catch (Exception e) {
            exception = e;
            //异常的时候尝试反射,允许自己写实现类传递进来
        }
    }
    /**
     * 获取Page参数
     *
     * @return
     */
    public static  Page getLocalPage() {
        return LOCAL_PAGE.get();
    }

    public static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }

    /**
     * 移除本地变量
     */
    public static void clearLocalPage() {
        LOCAL_PAGE.remove();
    }

    /**
     * 是否已经处理过
     *
     * @param ms
     * @return
     */
    public boolean isPageSqlSource(MappedStatement ms) {
        if (ms.getSqlSource() instanceof PageSqlSource) {
            return true;
        }
        return false;
    }
    /**
     * 修改SqlSource
     *
     * @param ms
     * @throws Throwable
     */
     //这里修改后才会在对应的sql中加上limit参数
    public void processMappedStatement(MappedStatement ms) throws Throwable {
        SqlSource sqlSource = ms.getSqlSource();
        
        MetaObject msObject = SystemMetaObject.forObject(ms);
        SqlSource pageSqlSource;
        if (sqlSource instanceof StaticSqlSource) {
            pageSqlSource = new PageStaticSqlSource((StaticSqlSource) sqlSource);
        } else if (sqlSource instanceof RawSqlSource) {
            //我们来看这个
            pageSqlSource = new PageRawSqlSource((RawSqlSource) sqlSource);
        } else if (sqlSource instanceof ProviderSqlSource) {
            pageSqlSource = new PageProviderSqlSource((ProviderSqlSource) sqlSource);
        } else if (sqlSource instanceof DynamicSqlSource) {
            pageSqlSource = new PageDynamicSqlSource((DynamicSqlSource) sqlSource);
        } else {
            throw new RuntimeException("无法处理该类型[" + sqlSource.getClass() + "]的SqlSource");
        }
        //这里将新的SqlSource放到MappedStatement中,这样当真正的Excutor执行时,从MappedStatement中getBoundSql时,实际是从sqlSource中getBoundSql获取,而我们已经将我们的sqlSource放进去了,所以会执行对应的sql
        msObject.setValue("sqlSource", pageSqlSource);
        //由于count查询需要修改返回值,因此这里要创建一个Count查询的MS
        msCountMap.put(ms.getId(), MSUtils.newCountMappedStatement(ms));
    }

    /**
     * 获取分页参数
     *
     * @param args
     * @return 返回Page对象
     */
    public Page getPage(Object[] args) {
        //就是从ThreadLocal中拿到,我们最开始放进去的了
        Page page = getLocalPage();
        //因为我设置了,所以不会走这里
        if (page == null || page.isOrderByOnly()) {
            Page oldPage = page;
            //这种情况下,page.isOrderByOnly()必然为true,所以不用写到条件中
            if ((args[2] == null || args[2] == RowBounds.DEFAULT) && page != null) {
                return oldPage;
            }
            if (args[2] instanceof RowBounds && args[2] != RowBounds.DEFAULT) {
                RowBounds rowBounds = (RowBounds) args[2];
                if (offsetAsPageNum) {
                    page = new Page(rowBounds.getOffset(), rowBounds.getLimit(), rowBoundsWithCount);
                } else {
                    page = new Page(new int[]{rowBounds.getOffset(), rowBounds.getLimit()}, rowBoundsWithCount);
                    //offsetAsPageNum=false的时候,由于PageNum问题,不能使用reasonable,这里会强制为false
                    page.setReasonable(false);
                }
            } else {
                try {
                    page = getPageFromObject(args[1]);
                } catch (Exception e) {
                    return null;
                }
            }
            if (oldsPage != null) {
                page.setOrderBy(oldPage.getOrderBy());
            }
            setLocalPage(page);
        }
        //分页合理化
        if (page.getReasonable() == null) {
            //设置为false
            page.setReasonable(reasonable);
        }
        //当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
        if (page.getPageSizeZero() == null) {
            //设置为false
            //当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
            page.setPageSizeZero(pageSizeZero);
        }
        return page;
    }

    /**
     * Mybatis拦截器方法,这一步嵌套为了在出现异常时也可以清空Threadlocal
     *
     * @param invocation 拦截器入参
     * @return 返回执行结果
     * @throws Throwable 抛出异常
     */
    public Object processPage(Invocation invocation) throws Throwable {
        try {
            Object result = _processPage(invocation);
            return result;
        } finally {
            clearLocalPage();
        }
    }

    /**
     * Mybatis拦截器方法
     *
     * @param invocation 拦截器入参
     * @return 返回执行结果
     * @throws Throwable 抛出异常
     */
     //从Pagehepler中的intercept到这里
     //参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
    private Object _processPage(Invocation invocation) throws Throwable {
        //{MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
        final Object[] args = invocation.getArgs();
        Page page = null;
        //支持方法参数时,会先尝试获取Page
        //默认是false
        if (supportMethodsArguments) {
            page = getPage(args);
        }
        //分页信息
        RowBounds rowBounds = (RowBounds) args[2];
        //支持方法参数时,如果page == null就说明没有分页条件,不需要分页查询
        if ((supportMethodsArguments && page == null)
                //当不支持分页参数时,判断LocalPage和RowBounds判断是否需要分页
                || (!supportMethodsArguments && SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT)) {
            return invocation.proceed();
        } else {
            //不支持分页参数时,page==null,这里需要获取
            //实际会进入到这里
            if (!supportMethodsArguments && page == null) {
                //从
                page = getPage(args);
            }
            //开始真正执行分页
            return doProcessPage(invocation, page, args);
        }
    }

    /**
     * Mybatis拦截器方法
     *
     * @param invocation 拦截器入参
     * @return 返回执行结果
     * @throws Throwable 抛出异常
     */
     //参数是target=CachingExecutor,method=Executor中指定的query方法方法,args={MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
     //args=MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
    private Page doProcessPage(Invocation invocation, Page page, Object[] args) throws Throwable {
        //保存RowBounds状态
        RowBounds rowBounds = (RowBounds) args[2];
        //获取原始的ms
        MappedStatement ms = (MappedStatement) args[0];
        //判断并处理为PageSqlSource
        if (!isPageSqlSource(ms)) {
            //这里是设置对应的SqlSource,方便分页,里面有原始时期sql
            processMappedStatement(ms);
        }
        //设置当前的parser,后面每次使用前都会set,ThreadLocal的值不会产生不良影响
        ((PageSqlSource)ms.getSqlSource()).setParser(parser);
        try {
            //忽略RowBounds-否则会进行Mybatis自带的内存分页
            args[2] = RowBounds.DEFAULT;
            //如果只进行排序 或 pageSizeZero的判断
            if (isQueryOnly(page)) {
                return doQueryOnly(page, invocation);
            }

            //简单的通过total的值来判断是否进行count查询
            if (page.isCount()) {
                page.setCountSignal(Boolean.TRUE);
                //替换MS
                args[0] = msCountMap.get(ms.getId());
                //查询总数
                Object result = invocation.proceed();
                //还原ms
                args[0] = ms;
                //设置总数
                page.setTotal((Integer) ((List) result).get(0));
                if (page.getTotal() == 0) {
                    return page;
                }
            } else {
                page.setTotal(-1l);
            }
            //pageSize>0的时候执行分页查询,pageSize<=0的时候不执行相当于可能只返回了一个count if page.getpagesize> 0 &&
                    ((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0)
                            || rowBounds != RowBounds.DEFAULT)) {
                //将参数中的MappedStatement替换为新的qs
                page.setCountSignal(null);
                //这里的args[1]就是query方法
                //实际调用了PageStaticSqlSource中的getPageBoundSql对原始sql进行拼接,
                //并将对应的当前页和每页大小设置到了ParameterMapping中,key分别是First_PageHelper、Second_PageHelper,value对应的是当前页和每页大小
                //参看PageStaticSqlSource代码块
                BoundSql boundSql = ms.getBoundSql(args[1]);
                //这里是将startRow、pageSize放进去,其实就是limit后的两个参数,并重新赋值给原始参数,这里相当于修改了原始参数
                args[1] = parser.setPageParameter(ms, args[1], boundSql, page);
                page.setCountSignal(Boolean.FALSE);
                //执行分页查询
                //开始执行分页
                //开始执行真正的query方法,在进行参数化时,调用DefaultParameterHandler的setParameters设置原始值和分页参数值,真正是由TypeHandler(BaseTypeHandler)的setParameters进行设置原始值和分页参数值
                //然后调用PreparedStatement的execute方法从而进行分页
                Object result = invocation.proceed();
                //得到处理结果,最后返回的是一个page,但是page继承list
                page.addAll((List) result);
            }
        } finally {
            ((PageSqlSource)ms.getSqlSource()).removeParser();
        }

        //返回结果
        return page;
    }
}
public class PageStaticSqlSource extends PageSqlSource {
    private String sql;
    private List parameterMappings;
    private Configuration configuration;
    private SqlSource original;

    @SuppressWarnings("unchecked")
    public PageStaticSqlSource(StaticSqlSource sqlSource) {
        MetaObject metaObject = SystemMetaObject.forObject(sqlSource);
        //原始sql
        this.sql = (String) metaObject.getValue("sql");
        //所有的参数
        this.parameterMappings = (List) metaObject.getValue("parameterMappings");
        this.configuration = (Configuration) metaObject.getValue("configuration");
        this.original = sqlSource;
    }

    @Override
    protected BoundSql getPageBoundSql(Object parameterObject) {
        String tempSql = sql;
        String orderBy = PageHelper.getOrderBy();
        if (orderBy != null) {
            tempSql = OrderByParser.converToOrderBySql(sql, orderBy);
        }
        //调用MysqlParser的getPageSql方法,拼接了 limit ?,?
        tempSql = localParser.get().getPageSql(tempSql);
        //并将对应的当前页和每页大小设置到了ParameterMapping中,key分别是First_PageHelper、Second_PageHelper,value对应的是当前页和每页大小
        //这里的BoundSql设置好分页sql和后面的参数后,在mybatis中会进行set
        return new BoundSql(configuration, tempSql, localParser.get().getPageParameterMapping(configuration, original.getBoundSql(parameterObject)), parameterObject);
    }

}
public class MysqlParser extends AbstractParser {
    @Override
    public String getPageSql(String sql) {
        StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
        sqlBuilder.append(sql);
        sqlBuilder.append(" limit ?,?");
        return sqlBuilder.toString();
    }

    @Override
    public Map setPageParameter(MappedStatement ms, Object parameterObject, BoundSql boundSql, Page page) {
        Map paramMap = super.setPageParameter(ms, parameterObject, boundSql, page);
        paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());
        paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());
        return paramMap;
    }
}

简单理解就是:

通过PageHelper的startPage方法,将当前页和每页大小等字段放到ThreadLocal中。

PageHelper 实现了 Mybatis的Interceptor接口,在创建CachingExecutor时调用Interceptor的plugin方法生成CachingExecutor的代理对象。

当用户执行查询的时候PageHelper的intercept会执行(实际就是invoke方法),然后创建sqlUtil并执行doProcessPage方法开始处理分页数据。

在doProcessPage方法中,通过创建新的PageSqlSource取代掉MappedStatement中原来的SqlSource,新的PageSqlSource中的BoundSql和parameterMappings已经是PageHelper处理后的,加上了limit ?,?和对应的分页参数(通过对应数据库的解析器,例如MysqlParser修改sql和分页参数)。

同时在doProcessPage方法中,在执行真正的CachingExecutor的query方法前修改了用户的参数传递,在原来基础上增加了key=First_PageHelper,value=startRow、key=Second_PageHelper,value=pageSize

当真正的CachingExecutor的query方法执行时,通过MappedStatement拿到的BoundSql实际是从新的SqlSource中的getPageBoundSql中拿的,而这个BoundSql实际我们将已经将sql加上了分页信息,同时拿到的parameterMappings集合参数中也加上了分页参数的key的名字(First_PageHelper、Second_PageHelper)。

然后在StatementHandler的parameterize参数化阶段通过parameterHandler的参数处理器给PreparedStatement中的每个位置的参数对应上,然后通过TypeHandler处理每个类型映射值,然后PreparedStatement执行获取最终结果,继续回到PageHelper的代理对象中,给page设置查询结果并返回。

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表