jar包
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
代码实现
在beanname为com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration的时候,最终会走到下面的方法中
PageHelperAutoConfiguration->addPageInterceptor():
// 新建一个拦截器
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
// 读取yml里pagehelper的配置
properties.putAll(pageHelperProperties());
properties.putAll(this.properties.getProperties());
// 初始化PageHelper类
interceptor.setProperties(properties);
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
// 将该拦截器注入到configuration的interceptorChain中
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
}
DemoController->test():
PageHelper.startPage(1, 10);
service.list();
PageMethod->startPage():
return startPage(pageNum, pageSize, DEFAULT_COUNT);
PageMethod->startPage():
// 关注前三个参数即可
// pageNum是页码,pageSize是每页显示数量,count是否进行count查询
return startPage(pageNum, pageSize, count, null, null);
PageMethod->startPage():
......
setLocalPage(page);
PageMethod->setLocalPage():
// LOCAL_PAGE是一个ThreadLocal变量,这就是为什么PageHepler只对当前线程起作用的原因了
LOCAL_PAGE.set(page);
// 之前所有sqlSession都会被代理,所以会进入invoke方法
SqlSessionTemplate->SqlSessionInterceptor->invoke():
// 每一条线程都会创建一个单独的sqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator); // --1
Object result = method.invoke(sqlSession, args); // --2
......
// 最终会从连接池释放连接
// 连接池有个connections[],启动的时候connections[0]=e,poolingCount=1
// 查询时poolingCount=0,temp=e,connections[0]=null,return temp
// 执行sqlSession.close之后,connections[0]=e,poolingCount=1
// 这就是连接池的意义了
finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
SqlSessionUtils->getSqlSession(): // --1
session = sessionFactory.openSession(executorType);
DefaultSqlSessionFactory->openSession():
return openSessionFromDataSource(execType, null, false);
DefaultSqlSessionFactory->openSessionFromDataSource():
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
MybatisConfiguration->newExecutor():
// 通过适配器模式初始化执行器
......
// pageInterceptor拦截器动态代理
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
Plugin->wrap():
// PageInterceptor的@Intercepts信息,这里的key是Signature.type(Executor类),值是被拦截的方法及参数(query方法)
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// target是前面的executor
Class<?> type = target.getClass();
// 获取目标类及父类所有实现的接口(即Executor接口),返回signatureMap包含该接口类的类
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
// 有的话,就创建动态代理,Plugin作为代理类
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
DefaultSqlSession->selectList(): // --2
return this.selectList(statement, parameter, RowBounds.DEFAULT);
DefaultSqlSession->selectList():
MappedStatement ms = configuration.getMappedStatement(statement);
// 因为executor被代理了,所以进入Plugin.invoke()方法
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
Plugin->invoke():
// signatureMap.get(Executor)返回不为空
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
// 现在是query方法,执行intercept方法
if (methods != null && methods.contains(method)) {
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
PageInterceptor->intercept():
try {
// 调用方法判断是否需要进行分页,如果不需要,直接返回结果
// LOCAL_PAGE.get()如果为空直接返回true
if (!dialect.skip(ms, parameter, rowBounds)) {
// 判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
// 查询总数
Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
// 处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
// 当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
// 这边会做很多的事情:主要有重写BoundSql,绑定参数,根据设置值优化sql等等
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
// 不需要分页则直接查询
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if(dialect != null){
// 执行LOCAL_PAGE.remove()方法,移除当前线程中的Page信息
dialect.afterAll();
}
}
记录: PageHelper包关键类与作用:
- PageInterceptor
- Dialect是数据库方言,不同的数据库拼接不同的分页语句,拼接参数
- 当是query方法时,走拦截器
- PageHelper 当前线程分页操作
- PageMethod 其下有个ThreadLocal型变量LOCAL_PAGE,在执行startPage()方法时调用
setLocalPage(page)设置page信息,如果当前线程没有page信息,则不分页;当拦截生效,其finally代码块执行LOCAL_PAGE.remove()移除page信息,这就是分页只针对当前线程且只对一条select sql生效的原因了 - Page 记录总页数、总条数、当前页数、当前条数、起始条数、起始页数等信息
总结:
PageHelper在启动时给configuration的interceptorChain增加拦截器,查询时通过动态代理拦截query方法,进入PageInterceptor的intercept方法,对原来查询的sql语句,参数值修改,最终返回executor.query
sql打印类PerformanceInterceptor实现原理与PageHelper一样一样的