一、startPage 做了什么
PageHelper.startPage(1,20);
这是 PageHelper 举荐的应用形式,后续的第一个查询方法会分页。
察看这个办法的外部逻辑
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count) {
// 封装成 page 对象
Page<E> page = new Page<E>(pageNum, pageSize, count);
// == 将 page 对象存在 `PageMethod#LOCAL_PAGE`——这是一个 ThreadLocal
setLocalPage(page);
return page;
}
二、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<Interceptor> 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 的要害办法进行拦挡,官网有具体介绍。