如果我们要在mybatis中使用PageHelper,我们第一步需要将PageHelper配置到mybatis的配置中
<plugins> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql"/> </plugin> </plugins> |
还记得上篇文章中提到,在进行创建SqlSessionFactoryBean的时候会解析配置文件,然后会将plugin加入到interceptorChain中。而且我们也提到,在SqlSession进行doQuery的时候调用的是executor的query方法,而executor的创建是通过Configuration创建的,那么我们下面进入到Configuration的newExecutor的方法中。如下
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; } |
从其中可以看到会调用所有的interceptor,然后调用interceptor的plugin方法,我们进入到PageHelper的plugin
public Object plugin(Object target) { if (target instanceof Executor) { //通过调用mybatis的plugin方法进行包装。 return Plugin.wrap(target, this); } else { return target; } } |
进入Plugin的wrap方法
public static Object wrap(Object target, Interceptor interceptor) { //该方法是通过当前的拦截器,获取需要拦截的方法的入参,方法名等 //通过解析Intercepts注解。 //key为Executor.class, value为,配置的query,并且参数全部匹配 Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor); // Class<?> type = target.getClass(); //查看当前的目标对象的实现的接口以及父类实现的接口,看是否在上面的 //map中。 Class<?>[] interfaces = getAllInterfaces(type, signatureMap); //存在,说明当前需要代理,返回代理对象。代理的尸体类为Plugin // return interfaces.length > 0 ? Proxy.newProxyInstance(type.getClassLoader(), interfaces, new Plugin(target, interceptor, signatureMap)) : target; } |
PageHelper对应的注解如下
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}))
通过上面我们可以知道,调用query(MappedStatement, Object, RowBounds, ResultHandler)
下面进入Plugin的invoke方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //首先从刚才的map中查找是否需要进行拦截,即 //是不是调用query(MappedStatement, Object, RowBounds, ResultHandler) Set<Method> methods = (Set)this.signatureMap.get(method.getDeclaringClass()); return methods != null && methods.contains(method) ? this.interceptor.intercept(new Invocation(this.target, method, args)) : method.invoke(this.target, args); } catch (Exception var5) { throw ExceptionUtil.unwrapThrowable(var5); } } |
通过上面可知,在调用query(MappedStatement, Object, RowBounds, ResultHandler)
会调用PageHelper的intercept方法。
public Object intercept(Invocation invocation) throws Throwable { if (autoDialect) { //初始化一些数据,获取dataSource等。 initSqlUtil(invocation); } return sqlUtil.processPage(invocation); } /** * 初始化sqlUtil * * @param invocation */ public synchronized void initSqlUtil(Invocation invocation) { if (sqlUtil == null) { String url = null; try { MappedStatement ms = (MappedStatement) invocation.getArgs()[0]; MetaObject msObject = SystemMetaObject.forObject(ms); DataSource dataSource = (DataSource) msObject.getValue("configuration.environment.dataSource"); url = dataSource.getConnection().getMetaData().getURL(); } catch (SQLException e) { throw new RuntimeException("分页插件初始化异常:" + e.getMessage()); } if (url == null || url.length() == 0) { throw new RuntimeException("无法自动获取jdbcUrl,请在分页插件中配置dialect参数!"); } String dialect = Dialect.fromJdbcUrl(url); if (dialect == null) { throw new RuntimeException("无法自动获取数据库类型,请通过dialect参数指定!"); } sqlUtil = new SqlUtil(dialect); sqlUtil.setProperties(properties); properties = null; autoDialect = false; } } |
进入到SqlUtil中的processPage
public Object processPage(Invocation invocation) throws Throwable { try { Object result = _processPage(invocation); return result; } finally { //清除线程变量 clearLocalPage(); OrderByHelper.clear(); } } |
进入processPage
private Object _processPage(Invocation invocation) throws Throwable { //获取执行当前方法的入参,由于当前已经限定了入参的个数以及各个类型, //所以可以强转 final Object[] args = invocation.getArgs();
RowBounds rowBounds = (RowBounds) args[2]; //判断是否需要通过pageHelper进行分页。 if (SqlUtil.getLocalPage() == null && rowBounds == RowBounds.DEFAULT) { if (OrderByHelper.getOrderBy() != null) { OrderByHelper.processIntercept(invocation); } return invocation.proceed(); } else { //获取原始的ms MappedStatement ms = (MappedStatement) args[0]; //判断并处理为PageSqlSource if (!isPageSqlSource(ms)) { processMappedStatement(ms, parser); } //忽略RowBounds-否则会进行Mybatis自带的内存分页 args[2] = RowBounds.DEFAULT; //分页信息 Page page = getPage(rowBounds); //pageSizeZero的判断 if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) { COUNT.set(null); //执行正常(不分页)查询 Object result = invocation.proceed(); //得到处理结果 page.addAll((List) result); //相当于查询第一页 page.setPageNum(1); //这种情况相当于pageSize=total page.setPageSize(page.size()); //仍然要设置total page.setTotal(page.size()); //返回结果仍然为Page类型 - 便于后面对接收类型的统一处理 return page; }
//简单的通过total的值来判断是否进行count查询 if (page.isCount()) { COUNT.set(Boolean.TRUE); //替换MS,之前上面的processMappedStatement修改的MappedStatement 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; } } //pageSize>0的时候执行分页查询,pageSize<=0的时候不执行相当于可能只返回了一个count if (page.getPageSize() > 0 && ((rowBounds == RowBounds.DEFAULT && page.getPageNum() > 0) || rowBounds != RowBounds.DEFAULT)) { //将参数中的MappedStatement替换为新的qs COUNT.set(null); BoundSql boundSql = ms.getBoundSql(args[1]); args[1] = parser.setPageParameter(ms, args[1], boundSql, page); COUNT.set(Boolean.FALSE); //执行分页查询 Object result = invocation.proceed(); //得到处理结果 page.addAll((List) result); } //返回结果 return page; } } |
其中的if (!isPageSqlSource(ms)) {
processMappedStatement(ms, parser);
}这句话很关键。会在这里创建一个新的MappedStatement。
public static void processMappedStatement(MappedStatement ms, Parser parser) throws Throwable { SqlSource sqlSource = ms.getSqlSource(); MetaObject msObject = SystemMetaObject.forObject(ms); SqlSource tempSqlSource = sqlSource; if (sqlSource instanceof OrderBySqlSource) { tempSqlSource = ((OrderBySqlSource) tempSqlSource).getOriginal(); } SqlSource pageSqlSource; if (tempSqlSource instanceof StaticSqlSource) { pageSqlSource = new PageStaticSqlSource((StaticSqlSource) tempSqlSource, parser); } else if (tempSqlSource instanceof RawSqlSource) { pageSqlSource = new PageRawSqlSource((RawSqlSource) tempSqlSource, parser); } else if (tempSqlSource instanceof ProviderSqlSource) { pageSqlSource = new PageProviderSqlSource((ProviderSqlSource) tempSqlSource, parser); } else if (tempSqlSource instanceof DynamicSqlSource) { pageSqlSource = new PageDynamicSqlSource((DynamicSqlSource) tempSqlSource, parser); } else { throw new RuntimeException("无法处理该类型[" + sqlSource.getClass() + "]的SqlSource"); } msObject.setValue("sqlSource", pageSqlSource); //由于count查询需要修改返回值,因此这里要创建一个Count查询的MS msCountMap.put(ms.getId(), MSUtils.newCountMappedStatement(ms)); } |
通过上面我们可以看到,其实他的主要方式是通过新建替换MappedStatement来进行修改sql语句从而实现分页的。