1.添加plugin
要使用PageHelper首先在mybatis的全局配置文件中配置:
<plugins>
<!-- com.github.pagehelper为PageHelper类所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql" />
<!-- 该参数默认为false -->
<!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
<!-- 和startPage中的pageNum效果一样 -->
<property name="offsetAsPageNum" value="true" />
<!-- 该参数默认为false -->
<!-- 设置为true时,使用RowBounds分页会进行count查询 -->
<property name="rowBoundsWithCount" value="true" />
<!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
<!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) -->
<property name="pageSizeZero" value="true" />
<!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
<!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
<!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
<property name="reasonable" value="false" />
<!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
<!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 -->
<!-- 不理解该含义的前提下,不要随便复制该配置 -->
<property name="params" value="pageNum=start;pageSize=limit;" />
<!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
<property name="returnPageInfo" value="check" />
</plugin>
2.加载流程
// 获取配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis/mybatis-config.xml");
// 通过加载配置文件获取SqlSessionFactory对象
// 将PageHelper拦截器加载到了拦截器链中
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 获取SqlSession对象 获取了一个Executor的增强代理类
SqlSession session = factory.openSession();
// 将 pageNum和pageSize封装到了 Page对象中,同时将Page对象保存到了当前线程的本地变量中的。
PageHelper.startPage(1, 5);
session.selectList("com.bobo.UserMapper.query");
代码跟踪build
代码跟踪parse
代码跟踪parseConfiguration
代码跟踪pluginElement
private void pluginElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 获取到内容:com.github.pagehelper.PageHelper
String interceptor = child.getStringAttribute("interceptor");
// 获取配置的属性信息
Properties properties = child.getChildrenAsProperties();
// 创建的拦截器实例
Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
// 将属性和拦截器绑定
interceptorInstance.setProperties(properties);
// 这个方法需要进入查看
configuration.addInterceptor(interceptorInstance);
}
}
}
代码跟踪addInterceptor
public void addInterceptor(Interceptor interceptor) {
// 将拦截器添加到了 拦截器链中 而拦截器链本质上就是一个List有序集合
interceptorChain.addInterceptor(interceptor);
}
小结:通过SqlSessionFactory对象的获取,我们加载了全局配置文件及映射文件同时还将配置的拦截器添加到了拦截器链中。
3.PageHelper定义的方法以及属性
@Intercepts(
@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class,
Object.class,
RowBounds.class,
ResultHandler.class}
))
上面代码的意思是,这个拦截器拦截的是Executor这个对象中的query(MappedStatement ms,Object o,RowBounds ob ResultHandler rh)
这个方法。
** 4.Executor**
讲到Executor,你是不是有点印象了?是的,在mybaits源码甚至是JDBC操作中,它都是常见的,执行器在mybaits中有:
默认为SimpleExecutor
批处理BatchExecutor
可重用ReuseExecutor
***启用于二级缓存时的执行器CachingExecutor ***
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;
}
**源码跟踪openSession **
SqlSession session = factory.openSession();
源码跟踪openSessionFromDataSource
源码跟踪newExecutor
源码跟踪pluginAll
源码跟踪plugin
到了PageHelper实现类里了。把PageHelper这个接口自身传入了wrap里。
源码跟踪wrap
到此我们明白了,Executor对象其实被我们生存的代理类增强了
实际上在invoke中执行的方法是接口中的方法,而这里的接口是PageHelper,所以前面我们看到的PageHelper里的方法,动态代理的被执行。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 如果是定义的拦截的方法 就执行intercept方法
if (methods != null && methods.contains(method)) {
// 进入查看 该方法增强
return interceptor.intercept(new Invocation(target, method, args));
}
// 不是需要拦截的方法 直接执行
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
源码跟踪intercept
/**
* Mybatis拦截器方法
*
* @param invocation 拦截器入参
* @return 返回执行结果
* @throws Throwable 抛出异常
*/
public Object intercept(Invocation invocation) throws Throwable {
if (autoRuntimeDialect) {
SqlUtil sqlUtil = getSqlUtil(invocation);
return sqlUtil.processPage(invocation);
} else {
if (autoDialect) {
initSqlUtil(invocation);
}
return sqlUtil.processPage(invocation);
}
}