目录
Mybatis原理
PageHelper使用
添加依赖
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.6</version>
</dependency>
加入插件
在springboot项目中的配置类这样写
public SqlSessionFactory sqlSessionFactoryBean() {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//分页插件
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
properties.setProperty("reasonable", "true");
properties.setProperty("supportMethodsArguments", "true");
properties.setProperty("returnPageInfo", "check");
properties.setProperty("params", "count=countSql");
pageInterceptor.setProperties(properties);
//添加插件
bean.setPlugins(new Interceptor[]{pageInterceptor});
//添加XML目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
try {
bean.setMapperLocations(resolver.getResources("../../..*.xml"));
return bean.getObject();
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
使用
PageHelper.startPage(pageNum, pageSize);
List<Model> list = ServiceImpl.method(..args);
PageInfo<Model> pageInfo = new PageInfo<Model>(list);
PageHelper.clearPage();
return pageInfo;
分析
我们首先看到配置类中配置SqlSessionFactory 的时候,调用添加插件的方法,添加了一个拦截器链。
添加的是这样一个拦截器
@Intercepts(
{
@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}),
}
)
我们可以看到拦截的是Executer,拦截的方法是query,就是查询方法,拦截它干啥?
我们看一下mybatis生产sqlsession的时候发生了什么?
这里创建了一个executor,进入查看。
发现是拦截器链的其中一个方法返回的,这个拦截器链中包含我们在配置类中加入的拦截器,进入查看。
发现返回的结果是每个拦截器的plugin方法处理一遍的executor,再次我们只关心PageHelper的拦截器作用的结果,那么看PageInterceptor中的plugin方法做了什么。
这个Plugin是mybatis中的类,PageInterceptor调用这个方法,要把自己作为参数生产一个执行器的包装类。
这是执行代码
@Override
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,那执行的过程是什么样子的呢?
以上是设置了分页之后,会走的路径,在intercept()方法中。
此时,还未拼接sql语句,进入函数,这个函数取分页参数并设置分页参数。
为什么会有分页参数呢?进入函数。
发现是从ThreadLocal中获取的。
PageHelper.startPage(pageNum, pageSize);
它具体操作如下,发现我们设置的参数封装到了page中,然后加入到了ThreadLocal中。
然后继续,到返回的时候进入这个函数。
往下走可以看到
进入函数一直跟踪,发现
在这里拼接了分页语句。
于是最后执行的其实是拼接之后的语句,占位符的数据即分页参数,作为参数传了进去。
总结
跟其它Mybatis Plugin一样,我们需要在Mybatis的配置文件或配置类中注册需要使用的Plugin,PageHelper5.0版本中对应的Plugin实现类就是com.github.pagehelper.PageInterceptor。Mybatis的Plugin我们说是Plugin,实际上对应的却是org.apache.ibatis.plugin.Interceptor接口,因为Interceptor的核心是其中的plugin(Object target)方法,而对于plugin(Object target)方法的实现,我们在需要对对应的对象进行拦截时会通过org.apache.ibatis.plugin.Plugin的静态方法wrap(Object target, Interceptor interceptor)返回一个代理对象,而方法入参就是当前的Interceptor实现类。
PageHelper拦截的是org.apache.ibatis.executor.Executor的query方法,其传参的核心原理是通过ThreadLocal进行的。当我们需要对某个查询进行分页查询时,我们可以在调用Mapper进行查询前调用一次PageHelper.startPage(..),这样PageHelper会把分页信息存入一个ThreadLocal变量中。在拦截到Executor的query方法执行时会从对应的ThreadLocal中获取分页信息,获取到了,则进行分页处理。