1. pageHelper 的依赖引入
pom.xml 中引入pageHelper依赖:
1. 如果是springboot, 则可以直接引入 pagehelper-spring-boot-starter, 它会帮我们省去许多不必要的配置。
2. 如果是普通的springmvc 类的项目,则引入 pageHelper 即可。
2. pagehelper插件配置
1. 如果是springboot,则直接配置几个配置项即可:
简单回顾看下db配置:
并在配置数据源的时候,将mybatis配置文件指向以上文件。
3. pagehelper 的使用
使用的时候,只需在查询list前,调用 startPage 设置分页信息,即可使用分页功能。
即使用时, 只需提前声明要分页的信息, 得到的结果就是有分页信息的了. 如果不想进行count, 只要查分页数据, 则调用: PageHelper.startPage(pageNum, pageSize, false); 即可, 避免了不必要的count消耗.
4. pageHelper 实现原理1: interceptor
mybatis 有个插件机制,可以支持外部应用进行任意扩展。它在启动的时候会将 interceptor 添加到mybatis的上下文中。然后在进行查询时再触发实例化动作.
借助springboot的自动配置, 获取mybatis的sqlSessionFactoryList, 依次将 pagehelper 接入其中。
这样, interceptor 就和executor绑定了, 后续的查询将会看到interceptor 的作用.
以上就是 pageHelper 的大体执行框架了:
1. 先解析各位置参数;
2. 初始化 pageHelper 实例, 即 dialect;
3. 调用方法判断是否需要进行分页,如果不需要,直接返回结果;
4. 判断是否要进行count, 如果需要则实现一次count, ;
5. 查询分页结果;
6. 封装带分页的结果返回;
下面我们就每个细节依次看看实现吧.
才上判定决定了后续的分页效果,主要是利用 ThreadLocal 来保存分页信息,从而与用户代码产生关联。
大体上讲就是分析sql, 如果是简单查询, 则直接将字段内容转换为 count(0) 即可, 这和我们普通认为的在select外部简单包一层还不太一样哦. 但是对于复杂查询咱们还是只能使用外包一层的实现方式了. 当然了,以上实现是针对mysql的,其他语言可能会有不一样的实现.
经过上面的sql重组之后,就可以得到具体分页的list数据了, 返回的也是list数据. 那么, 用户如何获取其他的分页信息呢? 比如count值去了哪里? 实际上, 在list 返回之后, 还有一个 afterPage 的动作要做, 而它的作用就是封装list 为带page信息的list.
至此, 一个完整的分页功能就完成了. 核心逻辑最开始也已看到, 就是判断是否需要分页, 是否需要count, 然后添加分页sql取数的这么个过程. 其本身并无太多银弹, 但却是能让我们节省不少时间. 另外就是, 在应对数据库可能发生切换的场景, 我们也可以无需更改此部分代码, 从而减轻了历史负担. 用用又何乐而不为呢?
最后, 我们再来看下oracle的核心分页的时候, 以理解pagehelper 的良苦用心.
5. oracle sql 变换
前面我们以mysql为样例, 看了pagehelper的转换过程, 其核心自然是 对count和select sql 的变换. 下面我们看看oracle如何变换吧!
从OracleDialect的实现中,我们看到它与mysql的差异仅在参数设置和获取分页sql时的差别, count 操作都是一样的. 虽然是这样, 但假设我们没有使用分页插件, 那么你会发现, 各个同学实现的count和分页查询相差甚大, 这必将给以后的改造带来许多麻烦, 这就没必要了.
pagehelper 支持的几个方言如下:
它们与oracle的实现方式都差不多,也就是说 count 都一样,只是分页的sql不一样而已。
遗留个思考题:pagehelper通过ThreadLocal来共享分页信息,那么它是何时进行清除的呢?如果不清理那不就乱套了吗?思考完成后点击以下查看答案!
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.12</version>
</dependency>
<!-- pageHelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.10</version>
</dependency>
# mybatis 相关配置
mybatis:
configuration-properties:
offsetAsPageNum: true
rowBoundsWithCount: true
reasonable: true
mapper-locations: mybatis/mapper/*.xml
# db 配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
username: root
password: 123
url: jdbc:mysql://localhost:3306/testdb?useUnicode=true&charactorEncoding=utf8&&serverTimezone=Asia/Shanghai
//分页查询
public PageResult pageQuery(CategoryPageQueryDTO categoryPageQueryDTO) {
// 1、开启分页(Mybatis自动拼接sql)
PageHelper.startPage(categoryPageQueryDTO.getPage(), categoryPageQueryDTO.getPageSize());
//下一条sql进行分页,自动加入limit关键字分页(接受数据)
Page page = categoryMapper.pageQuery(categoryPageQueryDTO);
return new PageResult(page.getTotal(), page.getResult());
}
4.1 springboot 中接入interceptor
springboot 中接入pagehelper非常简单, 主要受益于初始化的方式, 它会自动加载配置. // com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration#addPageInterceptor
@PostConstruct
public void addPageInterceptor() {
// 初始化 com.github.pagehelper.PageInterceptor
PageInterceptor interceptor = new PageInterceptor();
Properties properties = new Properties();
//先把一般方式配置的属性放进去
properties.putAll(pageHelperProperties());
//在把特殊配置放进去,由于close-conn 利用上面方式时,属性名就是 close-conn 而不是 closeConn,所以需要额外的一步
properties.putAll(this.properties.getProperties());
interceptor.setProperties(properties);
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
// 添加inteceptor到 mybatis 中
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
}
}
// org.apache.ibatis.session.Configuration#addInterceptor
public void addInterceptor(Interceptor interceptor) {
interceptorChain.addInterceptor(interceptor);
}
// org.apache.ibatis.plugin.InterceptorChain#addInterceptor
public void addInterceptor(Interceptor interceptor) {
// 使用 ArrayList 保存intceptor
interceptors.add(interceptor);
}
4.2 interceptor的初始化
将 interceptor 添加到mybatis上下文后, 会在每次调用查询时进行拦截请求, 它的初始化也会在这时候触发.// org.apache.ibatis.session.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);
}
// 以interceptorChain包装 executor, 以便inteceptor发挥作用
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
// org.apache.ibatis.plugin.InterceptorChain#pluginAll
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
// 使用plugin一层层包装 target, 具体实现为使用代理包装 target
// 所以, interceptor 的使用顺序是按照添加的顺序来的, 并不能自行设置
target = interceptor.plugin(target);
}
return target;
}
// com.github.pagehelper.PageInterceptor#plugin
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
// org.apache.ibatis.plugin.Plugin#wrap
public static Object wrap(Object target, Interceptor interceptor) {
// 获取注解中说明的方式列表 @Intercepts -> @Signature, 下面我们看 pageInterceptor的注解
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
// 过滤需要进行代理的接口, 而非全部代理
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
// 使用jdk方式生成动态代理
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
// 使用 Plugin 包装代理实现
new Plugin(target, interceptor, signatureMap));
}
return target;
}
// pageInterceptor的注解, 即定义要拦截的方法列表
@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}),
}
)
// 过滤代理的接口
private static Class<?>[] getAllInterfaces(Class<?> type, Map<Class<?>, Set<Method>> signatureMap) {
Set<Class<?>> interfaces = new HashSet<>();
while (type != null) {
for (Class<?> c : type.getInterfaces()) {
// 只有设置了的接口才会被添加
if (signatureMap.containsKey(c)) {
interfaces.add(c);
}
}
type = type.getSuperclass();
}
return interfaces.toArray(new Class<?>[interfaces.size()]);
}
4.3 interceptor的调用过程
在executor被代理后, 会继续执行查询动作, 这时就会被interceptor拦截了.// org.apache.ibatis.plugin.Plugin#invoke
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
Set<Method> methods = signatureMap.get(method.getDeclaringClass());
if (methods != null && methods.contains(method)) {
// 匹配的方法会被拦截, 即 query 方法
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
// pageHelper 正式起作用的入口
// com.github.pagehelper.PageInterceptor#intercept
@Override
public Object intercept(Invocation invocation) throws Throwable {
try {
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement) args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds) args[2];
ResultHandler resultHandler = (ResultHandler) args[3];
Executor executor = (Executor) invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
//由于逻辑关系,只会进入一次
if (args.length == 4) {
//4 个参数时
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
//6 个参数时
cacheKey = (CacheKey) args[4];
boundSql = (BoundSql) args[5];
}
checkDialectExists();
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
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);
}
}
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if(dialect != null){
dialect.afterAll();
}
}
}
4.4 是否跳过分页判定
首先会进行是否需要跳过分页逻辑,如果跳过, 则直接执行mybatis的核心逻辑继续查询. 而是否要跳过分页, 则是通过直接获取page分页参数来决定的,没有分页参数设置,则跳过, 否则执行分页查询. 这算是分页的一个入口判定呢。 /**
* 跳过 count 和 分页查询
*
* @param ms MappedStatement
* @param parameterObject 方法参数
* @param rowBounds 分页参数
* @return true 跳过,返回默认查询结果,false 执行分页查询
*/
// com.github.pagehelper.PageHelper#skip
@Override
public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
if (ms.getId().endsWith(MSUtils.COUNT)) {
throw new RuntimeException("在系统中发现了多个分页插件,请检查系统配置!");
}
// 如果 page 返回null, 则不需要进行分页, 即是否调用 PageHelper.start(pageNo, pageSize) 方法
Page page = pageParams.getPage(parameterObject, rowBounds);
if (page == null) {
return true;
} else {
//设置默认的 count 列
if (StringUtil.isEmpty(page.getCountColumn())) {
page.setCountColumn(pageParams.getCountColumn());
}
autoDialect.initDelegateDialect(ms);
return false;
}
}
// com.github.pagehelper.page.PageAutoDialect#initDelegateDialect
//多数据动态获取时,每次需要初始化
public void initDelegateDialect(MappedStatement ms) {
if (delegate == null) {
if (autoDialect) {
// 比如 MySqlDialect
this.delegate = getDialect(ms);
} else {
dialectThreadLocal.set(getDialect(ms));
}
}
}
/**
* 获取分页参数
*/
// com.github.pagehelper.page.PageParams#getPage
public Page getPage(Object parameterObject, RowBounds rowBounds) {
Page page = PageHelper.getLocalPage();
if (page == null) {
if (rowBounds != RowBounds.DEFAULT) {
if (offsetAsPageNum) {
page = new Page(rowBounds.getOffset(), rowBounds.getLimit(), rowBoundsWithCount);
} else {
page = new Page(new int[]{rowBounds.getOffset(), rowBounds.getLimit()}, rowBoundsWithCount);
//offsetAsPageNum=false的时候,由于PageNum问题,不能使用reasonable,这里会强制为false
page.setReasonable(false);
}
if(rowBounds instanceof PageRowBounds){
PageRowBounds pageRowBounds = (PageRowBounds)rowBounds;
page.setCount(pageRowBounds.getCount() == null || pageRowBounds.getCount());
}
} else if(parameterObject instanceof IPage || supportMethodsArguments){
try {
page = PageObjectUtil.getPageFromObject(parameterObject, false);
} catch (Exception e) {
return null;
}
}
if(page == null){
return null;
}
PageHelper.setLocalPage(page);
}
//分页合理化
if (page.getReasonable() == null) {
page.setReasonable(reasonable);
}
//当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
if (page.getPageSizeZero() == null) {
page.setPageSizeZero(pageSizeZero);
}
return page;
}
4.5 pageHelper 的 count 操作
判断是否是否需要count, 这些判定都会以 PageHelper 作为门面类进行接入, 而特殊地方则由具体方言实现.// com.github.pagehelper.PageHelper#beforeCount
@Override
public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
return autoDialect.getDelegate().beforeCount(ms, parameterObject, rowBounds);
}
// com.github.pagehelper.dialect.AbstractHelperDialect#beforeCount
@Override
public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
// 获取page参数信息, 该参数设置在 ThreadLocal 中
Page page = getLocalPage();
return !page.isOrderByOnly() && page.isCount();
}
// 如果需要进行count, 则需要自行组装count逻辑进行查询.
// com.github.pagehelper.PageInterceptor#count
private Long count(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
// 在原有list 查询后添加 _COUNT 代表count查询id
String countMsId = ms.getId() + countSuffix;
Long count;
//先判断是否存在手写的 count 查询
MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
if (countMs != null) {
count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
} else {
countMs = msCountMap.get(countMsId);
//自动创建
if (countMs == null) {
//根据当前的 ms 创建一个返回值为 Long 类型的 ms
countMs = MSUtils.newCountMappedStatement(ms, countMsId);
msCountMap.put(countMsId, countMs);
}
count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
}
return count;
}
// 创建count ms
// com.github.pagehelper.util.MSUtils#newCountMappedStatement(org.apache.ibatis.mapping.MappedStatement, java.lang.String)
public static MappedStatement newCountMappedStatement(MappedStatement ms, String newMsId) {
// 直接基于原有 sql 构建新的 MappedStatement
MappedStatement.Builder builder = new MappedStatement.Builder(ms.getConfiguration(), newMsId, ms.getSqlSource(), ms.getSqlCommandType());
builder.resource(ms.getResource());
// 注意此处并未使用到用户设置的分页参数
builder.fetchSize(ms.getFetchSize());
builder.statementType(ms.getStatementType());
builder.keyGenerator(ms.getKeyGenerator());
if (ms.getKeyProperties() != null && ms.getKeyProperties().length != 0) {
StringBuilder keyProperties = new StringBuilder();
for (String keyProperty : ms.getKeyProperties()) {
keyProperties.append(keyProperty).append(",");
}
keyProperties.delete(keyProperties.length() - 1, keyProperties.length());
builder.keyProperty(keyProperties.toString());
}
builder.timeout(ms.getTimeout());
builder.parameterMap(ms.getParameterMap());
//count查询返回值int
List<ResultMap> resultMaps = new ArrayList<ResultMap>();
ResultMap resultMap = new ResultMap.Builder(ms.getConfiguration(), ms.getId(), Long.class, EMPTY_RESULTMAPPING).build();
resultMaps.add(resultMap);
builder.resultMaps(resultMaps);
builder.resultSetType(ms.getResultSetType());
builder.cache(ms.getCache());
builder.flushCacheRequired(ms.isFlushCacheRequired());
builder.useCache(ms.isUseCache());
return builder.build();
}
/**
* 执行自动生成的 count 查询
*/
// com.github.pagehelper.util.ExecutorUtil#executeAutoCount
public static Long executeAutoCount(Dialect dialect, Executor executor, MappedStatement countMs,
Object parameter, BoundSql boundSql,
RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
//创建 count 查询的缓存 key
CacheKey countKey = executor.createCacheKey(countMs, parameter, RowBounds.DEFAULT, boundSql);
//调用方言获取 count sql
String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
//countKey.update(countSql);
BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
//当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
for (String key : additionalParameters.keySet()) {
countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行 count 查询
Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
Long count = (Long) ((List) countResultList).get(0);
return count;
}
// com.github.pagehelper.PageHelper#getCountSql
@Override
public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
// 委托给各方言实现 sql 组装
return autoDialect.getDelegate().getCountSql(ms, boundSql, parameterObject, rowBounds, countKey);
}
// com.github.pagehelper.dialect.AbstractHelperDialect#getCountSql
@Override
public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
Page<Object> page = getLocalPage();
String countColumn = page.getCountColumn();
if (StringUtil.isNotEmpty(countColumn)) {
return countSqlParser.getSmartCountSql(boundSql.getSql(), countColumn);
}
return countSqlParser.getSmartCountSql(boundSql.getSql());
}
/**
* 获取智能的countSql
*
* @param sql
* @param name 列名,默认 0
* @return
*/
// com.github.pagehelper.parser.CountSqlParser#getSmartCountSql(java.lang.String, java.lang.String)
public String getSmartCountSql(String sql, String name) {
//解析SQL
Statement stmt = null;
//特殊sql不需要去掉order by时,使用注释前缀
if(sql.indexOf(KEEP_ORDERBY) >= 0){
return getSimpleCountSql(sql, name);
}
try {
stmt = CCJSqlParserUtil.parse(sql);
} catch (Throwable e) {
//无法解析的用一般方法返回count语句
return getSimpleCountSql(sql, name);
}
Select select = (Select) stmt;
SelectBody selectBody = select.getSelectBody();
try {
//处理body-去order by
processSelectBody(selectBody);
} catch (Exception e) {
//当 sql 包含 group by 时,不去除 order by
return getSimpleCountSql(sql, name);
}
//处理with-去order by
processWithItemsList(select.getWithItemsList());
//处理为count查询
sqlToCount(select, name);
String result = select.toString();
return result;
}
/**
* 将sql转换为count查询
*
* @param select
*/
// com.github.pagehelper.parser.CountSqlParser#sqlToCount
public void sqlToCount(Select select, String name) {
SelectBody selectBody = select.getSelectBody();
// 是否能简化count查询
List<SelectItem> COUNT_ITEM = new ArrayList<SelectItem>();
// 如 select * from user 将会被转化为 select count(0) from user
COUNT_ITEM.add(new SelectExpressionItem(new Column("count(" + name +")")));
if (selectBody instanceof PlainSelect && isSimpleCount((PlainSelect) selectBody)) {
// 简单sql直接转换select字段为 count(0) 即可, 而这个sql是否支持这种方式则得仔细验证
((PlainSelect) selectBody).setSelectItems(COUNT_ITEM);
} else {
// 如果对于复杂的sql查询, 则只能在现有sql外围加一个 select count(0) from (xxxxx) as table_count
PlainSelect plainSelect = new PlainSelect();
SubSelect subSelect = new SubSelect();
subSelect.setSelectBody(selectBody);
subSelect.setAlias(TABLE_ALIAS);
// 将原sql作为临时表放入 plainSelect 中
plainSelect.setFromItem(subSelect);
plainSelect.setSelectItems(COUNT_ITEM);
// 替换原有 select
select.setSelectBody(plainSelect);
}
}
/**
* 是否可以用简单的count查询方式
*/
// net.sf.jsqlparser.statement.select.PlainSelect
public boolean isSimpleCount(PlainSelect select) {
//包含group by的时候不可以
if (select.getGroupBy() != null) {
return false;
}
//包含distinct的时候不可以
if (select.getDistinct() != null) {
return false;
}
for (SelectItem item : select.getSelectItems()) {
//select列中包含参数的时候不可以,否则会引起参数个数错误
if (item.toString().contains("?")) {
return false;
}
//如果查询列中包含函数,也不可以,函数可能会聚合列
if (item instanceof SelectExpressionItem) {
Expression expression = ((SelectExpressionItem) item).getExpression();
if (expression instanceof Function) {
String name = ((Function) expression).getName();
if (name != null) {
String NAME = name.toUpperCase();
if(skipFunctions.contains(NAME)){
//go on
} else if(falseFunctions.contains(NAME)){
return false;
} else {
for (String aggregateFunction : AGGREGATE_FUNCTIONS) {
if(NAME.startsWith(aggregateFunction)){
falseFunctions.add(NAME);
return false;
}
}
skipFunctions.add(NAME);
}
}
}
}
}
return true;
}
4.6 select list 的改装
在执行完count后, 分页的功能完成了一半. 我们可以给到用户这个计数值, 另外,我们可以根据该值得到后续分页还有多少数据, 如果没有自然不用再查了, 如果有则组装limit语句.// com.github.pagehelper.dialect.AbstractHelperDialect#afterCount
@Override
public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) {
Page page = getLocalPage();
page.setTotal(count);
if (rowBounds instanceof PageRowBounds) {
((PageRowBounds) rowBounds).setTotal(count);
}
//pageSize < 0 的时候,不执行分页查询
//pageSize = 0 的时候,还需要执行后续查询,但是不会分页
if (page.getPageSize() < 0) {
return false;
}
// 还没到最后一页, 则需要进行分页查询
return count > ((page.getPageNum() - 1) * page.getPageSize());
}
/**
* 分页查询
*/
public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql, CacheKey cacheKey) throws SQLException {
//判断是否需要进行分页查询
if (dialect.beforePage(ms, parameter, rowBounds)) {
//生成分页的缓存 key
CacheKey pageKey = cacheKey;
//处理参数对象, 将会加入 pageStart, pageSize 等参数
parameter = dialect.processParameterObject(ms, parameter, boundSql, pageKey);
//调用方言获取分页 sql
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, pageKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
//设置动态参数
for (String key : additionalParameters.keySet()) {
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//执行分页查询
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
}
// com.github.pagehelper.dialect.AbstractHelperDialect#processParameterObject
@Override
public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
//处理参数
Page page = getLocalPage();
//如果只是 order by 就不必处理参数
if (page.isOrderByOnly()) {
return parameterObject;
}
Map<String, Object> paramMap = null;
if (parameterObject == null) {
paramMap = new HashMap<String, Object>();
} else if (parameterObject instanceof Map) {
//解决不可变Map的情况
paramMap = new HashMap<String, Object>();
paramMap.putAll((Map) parameterObject);
} else {
paramMap = new HashMap<String, Object>();
//动态sql时的判断条件不会出现在ParameterMapping中,但是必须有,所以这里需要收集所有的getter属性
//TypeHandlerRegistry可以直接处理的会作为一个直接使用的对象进行处理
boolean hasTypeHandler = ms.getConfiguration().getTypeHandlerRegistry().hasTypeHandler(parameterObject.getClass());
MetaObject metaObject = MetaObjectUtil.forObject(parameterObject);
//需要针对注解形式的MyProviderSqlSource保存原值
if (!hasTypeHandler) {
for (String name : metaObject.getGetterNames()) {
paramMap.put(name, metaObject.getValue(name));
}
}
//下面这段方法,主要解决一个常见类型的参数时的问题
if (boundSql.getParameterMappings() != null && boundSql.getParameterMappings().size() > 0) {
for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
String name = parameterMapping.getProperty();
if (!name.equals(PAGEPARAMETER_FIRST)
&& !name.equals(PAGEPARAMETER_SECOND)
&& paramMap.get(name) == null) {
if (hasTypeHandler
|| parameterMapping.getJavaType().equals(parameterObject.getClass())) {
paramMap.put(name, parameterObject);
break;
}
}
}
}
}
return processPageParameter(ms, paramMap, page, boundSql, pageKey);
}
// 加入 page 参数
// com.github.pagehelper.dialect.helper.MySqlDialect#processPageParameter
@Override
public Object processPageParameter(MappedStatement ms, Map<String, Object> paramMap, Page page, BoundSql boundSql, CacheKey pageKey) {
// First_PageHelper, Second_PageHelper
paramMap.put(PAGEPARAMETER_FIRST, page.getStartRow());
paramMap.put(PAGEPARAMETER_SECOND, page.getPageSize());
//处理pageKey
pageKey.update(page.getStartRow());
pageKey.update(page.getPageSize());
//处理参数配置
if (boundSql.getParameterMappings() != null) {
List<ParameterMapping> newParameterMappings = new ArrayList<ParameterMapping>(boundSql.getParameterMappings());
if (page.getStartRow() == 0) {
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, Integer.class).build());
} else {
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_FIRST, Integer.class).build());
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, Integer.class).build());
}
MetaObject metaObject = MetaObjectUtil.forObject(boundSql);
metaObject.setValue("parameterMappings", newParameterMappings);
}
return paramMap;
}
// 组装分页sql
// com.github.pagehelper.dialect.AbstractHelperDialect#getPageSql
@Override
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
String sql = boundSql.getSql();
Page page = getLocalPage();
//支持 order by
String orderBy = page.getOrderBy();
if (StringUtil.isNotEmpty(orderBy)) {
pageKey.update(orderBy);
sql = OrderByParser.converToOrderBySql(sql, orderBy);
}
if (page.isOrderByOnly()) {
return sql;
}
return getPageSql(sql, page, pageKey);
}
// com.github.pagehelper.dialect.helper.MySqlDialect#getPageSql
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
// 分页sql拼接, limit xxx
if (page.getStartRow() == 0) {
sqlBuilder.append(" LIMIT ? ");
} else {
sqlBuilder.append(" LIMIT ?, ? ");
}
return sqlBuilder.toString();
}
// com.github.pagehelper.PageHelper#afterPage
@Override
public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
//这个方法即使不分页也会被执行,所以要判断 null
AbstractHelperDialect delegate = autoDialect.getDelegate();
if (delegate != null) {
return delegate.afterPage(pageList, parameterObject, rowBounds);
}
return pageList;
}
// com.github.pagehelper.dialect.AbstractHelperDialect#afterPage
@Override
public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
// 取出本线程的page变量, 放入list
Page page = getLocalPage();
if (page == null) {
return pageList;
}
page.addAll(pageList);
// count 值临时变换, 用于应对没有进行count的场景, 使外部表现一致
if (!page.isCount()) {
page.setTotal(-1);
} else if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) {
page.setTotal(pageList.size());
} else if(page.isOrderByOnly()){
page.setTotal(pageList.size());
}
return page;
}
// com.github.pagehelper.dialect.helper.OracleDialect
public class OracleDialect extends AbstractHelperDialect {
@Override
public Object processPageParameter(MappedStatement ms, Map<String, Object> paramMap, Page page, BoundSql boundSql, CacheKey pageKey) {
paramMap.put(PAGEPARAMETER_FIRST, page.getEndRow());
paramMap.put(PAGEPARAMETER_SECOND, page.getStartRow());
//处理pageKey
pageKey.update(page.getEndRow());
pageKey.update(page.getStartRow());
//处理参数配置
handleParameter(boundSql, ms);
return paramMap;
}
// 获取带分页的sql
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 120);
// 很明显, oracle 和 mysql 的分页实现是不一样的, oracle 使用 row_id 实现, 而 mysql 使用 limit 实现
sqlBuilder.append("SELECT * FROM ( ");
sqlBuilder.append(" SELECT TMP_PAGE.*, ROWNUM ROW_ID FROM ( ");
sqlBuilder.append(sql);
sqlBuilder.append(" ) TMP_PAGE)");
sqlBuilder.append(" WHERE ROW_ID <= ? AND ROW_ID > ?");
return sqlBuilder.toString();
}
}
// 实际上在每次运行完成pageInterceptor之后,都会在finnaly中进行一次清理工作
try {
// do page things
} finally {
// afterAll 即为清理任务
if(dialect != null){
dialect.afterAll();
}
}
// com.github.pagehelper.PageHelper#afterAll
@Override
public void afterAll() {
//这个方法即使不分页也会被执行,所以要判断 null
AbstractHelperDialect delegate = autoDialect.getDelegate();
if (delegate != null) {
// 默认为空
delegate.afterAll();
// delegate 移除,这里也是使用 ThreadLocal 实现,直接remove即可
autoDialect.clearDelegate();
}
// 清理 page对象,下次不再有该设置,也就是说 page 设置是一次性的
clearPage();
}
// com.github.pagehelper.page.PageMethod#clearPage
public static void clearPage() {
LOCAL_PAGE.remove();
}
// 下次再进行分页时,重新调用 PageHelper.startPage(x, x, x); 即可
- THE END -
最后修改:2024年4月23日


评论区暂未迁移
旧站评论表还没有完全接入新版接口层,这里会继续按数据库方式补齐。