本文已参与「新人创作礼」活动,一起开启掘金创作之路。
本篇内容主要记录mybatis插件篇之pagehelper的源码分析过程。
上篇文章已经描述pagehelper的分页功能如何使用了,那原理肯定得探索一下了。
jar包依赖
基于springboot 2.5.6 只贴和源码相关的了。
<!--mybatis-->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!--分页-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.0</version>
</dependency>
1. startPage和clearPage
1.1 PageHelper.startPage(1, 10);
/**
* 开始分页
*
* @param pageNum 页码
* @param pageSize 每页显示数量
* @param count 是否进行count查询
* @param reasonable 分页合理化,null时用默认配置
* @param pageSizeZero true且pageSize=0时返回全部结果,false时分页,null时用默认配置
*/
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
//构造方法 ##
Page<E> page = new Page<E>(pageNum, pageSize, count);
//合理化配置需要设置reasonable=true 页码<=0是转变为1
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
//当已经执行过orderBy的时候
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
//保存到本地线程变量
setLocalPage(page);
return page;
}
- 核心的顶层存储
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
//Page
public class Page<E> extends ArrayList<E> implements Closeable {
private static final long serialVersionUID = 1L;
/**
* 页码,从1开始
*/
private int pageNum;
/**
* 页面大小
*/
private int pageSize;
/**
* 起始行
*/
private long startRow;
/**
* 末行
*/
private long endRow;
/**
* 总数
*/
private long total;
/**
* 总页数
*/
private int pages;
/**
* 包含count查询
*/
private boolean count = true;
/**
* 分页合理化
*/
private Boolean reasonable;
/**
* 当设置为true的时候,如果pagesize设置为0(或RowBounds的limit=0),就不执行分页,返回全部结果
*/
private Boolean pageSizeZero;
/**
* 进行count查询的列名
*/
private String countColumn;
/**
* 排序
*/
private String orderBy;
/**
* 只增加排序
*/
private boolean orderByOnly;
/**
* sql拦截处理
*/
private BoundSqlInterceptor boundSqlInterceptor;
private transient BoundSqlInterceptor.Chain chain;
/**
* 分页实现类,可以使用 {@link com.github.pagehelper.page.PageAutoDialect} 类中注册的别名,例如 "mysql", "oracle"
*/
private String dialectClass;
}
1.2 PageHelper.clearPage();
//移除本地变量
public static void clearPage() {
LOCAL_PAGE.remove();
}
2.pagehelper的代码织入过程
2.1 由spring管理的interceptor bean初始化过程
回到mybatis源码的自动装配类MybatisAutoConfiguration
public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
this.properties = properties;
//根据类型取容器中找所有Interceptor接口的实现类 ### 大概看下
this.interceptors = interceptorsProvider.getIfAvailable();
//根据类型取容器中找所有TypeHandler接口的实现类
this.typeHandlers = typeHandlersProvider.getIfAvailable();
this.languageDrivers = languageDriversProvider.getIfAvailable();
this.resourceLoader = resourceLoader;
this.databaseIdProvider = databaseIdProvider.getIfAvailable();
this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
}
DefaultListableBeanFactory -> doResolveDependency
//获取泛型的class类型
Class<?> type = descriptor.getDependencyType();
//根据多个注入条件查询 ###
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
//DefaultListableBeanFactory#resolveMultipleBeans
//符合注入的候选bean会被通过getBean获取实例
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, componentType,
new MultiElementDescriptor(descriptor));
- 在初始化bean
sqlSessionFactory时
if (!ObjectUtils.isEmpty(this.interceptors)) {
factory.setPlugins(this.interceptors);
}
//SqlSessionFactoryBean#buildSqlSessionFactory
if (!isEmpty(this.plugins)) {
Stream.of(this.plugins).forEach(plugin -> {
//最终添加到InterceptorChain类
targetConfiguration.addInterceptor(plugin);
LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
});
}
这里指的注意的是,这里找到的interceptor实现是已经向容器中注册了的,也就是已经交给spring管理的interceptor bean
2.2 pagehelper的初始化过程
由于断点发现pagehelper并没有交由spring管理,那么它的入口只有以下几种情况
- 由开发者在代码里面手动注入,例如@Bean
- springboot项目中的自动装配类
EnableAutoConfiguration
于是在pagehelper-spring-boot-autoconfigure-1.4.0.jar/META-INF/spring.factories找到如下内容
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.github.pagehelper.autoconfigure.PageHelperAutoConfiguration
- pagehelper 入口
PageHelperAutoConfiguration
//该类实现了InitializingBean 于是直接找到这个回调
@Override
public void afterPropertiesSet() throws Exception {
//new一个Interceptor对象
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) {
org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
if (!containsInterceptor(configuration, interceptor)) {
//通过mybatis中的Configuration添加到InterceptorChain里面
configuration.addInterceptor(interceptor);
}
}
}
InterceptorChain#addInterceptor
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
//调用阶段封装 后面分析
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//添加到本地变量中
public void addInterceptor(Interceptor interceptor) {
interceptors.add(interceptor);
}
public List<Interceptor> getInterceptors() {
return Collections.unmodifiableList(interceptors);
}
}
到这里所有的interceptor实现就都已经保存到InterceptorChain中的list里面了,初始化阶段完成。
2.3 pagehelper的织入阶段
回顾一下mybatis的调用流程,从我们自己手写的xxxMapper接口开始
MapperProxy#invoke -> 内部类PlainMethodInvoker#invoke -> MapperMethod#execute -> SqlSessionTemplate#selectList-> 内部代理类SqlSessionInterceptor#invoke(创建新的sqlseesion,事务的自动提交在这里控制,执行器插件织入均在这里初始化) ->
DefaultSqlSession#selectList -> BaseExecutor#query(此处executor可被插件代理,甚至多层) -> 到对应的执行器例如SimpleExecutor#doQuery(预处理参数prepareStatement-> PreparedStatementHandler#parameterize中的parameterHandler可被多层代理) -> SimpleStatementHandler#query(随执行器而定,可被多层代理) -> DefaultResultSetHandler#handleResultSets(ResultSetHandler可被代理) -> 返回list
可被代理的 Executor,ParameterHandler,StatementHandler,ResultSetHandler均可被插件代理,本篇文章重点分析的是Executor
- 直接找到
SqlSessionTemplate内部类SqlSessionInterceptor#invoke创建sqlSeesion时会创建执行器的代码
//getSqlSession -> DefaultSqlSessionFactory#openSession -> openSessionFromDataSource
final Executor executor = configuration.newExecutor(tx, execType);
//根据不同的类型创建执行器
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;
}
- 再次来到
InterceptorChain类
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
//Interceptor接口有默认实现类
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
Plugin#wrap
public static Object wrap(Object target, Interceptor interceptor) {
//解析
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
Class<?> type = target.getClass();
//需要该类有接口才代理
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
//生成代理对象 invoke对象是Plugin
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
Plugin#getSignatureMap解析过程
//对应这个注解内容
@Intercepts(value = {@Signature(type = Executor.class, method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
private static Map<Class<?>, Set<Method>> getSignatureMap(Interceptor interceptor) {
//获取Intercepts注解
Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
// issue #251
if (interceptsAnnotation == null) {
throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());
}
//取出value 下面是for循环 也就是说一个实现了可以代理多个类和方法
Signature[] sigs = interceptsAnnotation.value();
Map<Class<?>, Set<Method>> signatureMap = new HashMap<>();
for (Signature sig : sigs) {
//不存在才走里面的逻辑
Set<Method> methods = MapUtil.computeIfAbsent(signatureMap, sig.type(), k -> new HashSet<>());
try {
//获取方法
Method method = sig.type().getMethod(sig.method(), sig.args());
methods.add(method);
} catch (NoSuchMethodException e) {
throw new PluginException("Could not find method on " + sig.type() + " named " + sig.method() + ". Cause: " + e, e);
}
}
return signatureMap;
}
从上述内容可以看到,Executor创建时会经过插件链路pluginAll方法生成代理类,invoke类对象是Plugin,那直接来到invoke类。
2.4 pagehelper的调用阶段
- 找到这里
BaseExecutor#query在有插件的实现下,被代理的情况下应该会到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)) {
//是就走这个 那直接来到初始化添加进来的PageInterceptor类
return interceptor.intercept(new Invocation(target, method, args));
}
return method.invoke(target, args);
} catch (Exception e) {
throw ExceptionUtil.unwrapThrowable(e);
}
}
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();
//对 boundSql 的拦截处理
if (dialect instanceof BoundSqlInterceptor.Chain) {
boundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.ORIGINAL, boundSql, cacheKey);
}
List resultList;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
if (!dialect.skip(ms, parameter, rowBounds)) {
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
//查询总数 ###
Long count = count(executor, ms, parameter, rowBounds, null, boundSql);
//处理查询总数,返回 true 时继续分页查询,false 时直接返回
if (!dialect.afterCount(count, parameter, rowBounds)) {
//当查询总数为 0 时,直接返回空的结果
return dialect.afterPage(new ArrayList(), parameter, rowBounds);
}
}
//原始sql查询
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();
}
}
}
2.5 PageInterceptor#count count查询阶段
private Long count(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
//countSuffix=_COUNT statementId变更
String countMsId = ms.getId() + countSuffix;
Long count;
//先判断是否存在手写的 count 查询 就是同一个statementId
MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
if (countMs != null) {
//存在就直接执行
count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
} else {
//pagehelper count statement本地存储map
if (msCountMap != null) {
countMs = msCountMap.get(countMsId);
}
//自动创建
if (countMs == null) {
//根据当前的 ms 创建一个返回值为 Long 类型的 MappedStatement
countMs = MSUtils.newCountMappedStatement(ms, countMsId);
if (msCountMap != null) {
//缓存到本地
msCountMap.put(countMsId, countMs);
}
}
//执行 ###
count = ExecutorUtil.executeAutoCount(this.dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
}
return count;
}
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 sql转变的过程 ###
String countSql = dialect.getCountSql(countMs, boundSql, parameter, rowBounds, countKey);
//countKey.update(countSql); 生成一个新的BoundSql
BoundSql countBoundSql = new BoundSql(countMs.getConfiguration(), countSql, boundSql.getParameterMappings(), parameter);
//当使用动态 SQL 时,可能会产生临时的参数,这些参数需要手动设置到新的 BoundSql 中
for (String key : additionalParameters.keySet()) {
countBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
//对 boundSql 的拦截处理
if (dialect instanceof BoundSqlInterceptor.Chain) {
countBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.COUNT_SQL, countBoundSql, countKey);
}
//执行 count 查询
Object countResultList = executor.query(countMs, parameter, RowBounds.DEFAULT, resultHandler, countKey, countBoundSql);
//某些数据(如 TDEngine)查询 count 无结果时返回 null
if (countResultList == null || ((List) countResultList).isEmpty()) {
return 0L;
}
return ((Number) ((List) countResultList).get(0)).longValue();
}
AbstractHelperDialect#getCountSql
@Override
public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
//获取本地线程变量
Page<Object> page = getLocalPage();
//可以自定义count指定列 例如:count(uid)
String countColumn = page.getCountColumn();
if (StringUtil.isNotEmpty(countColumn)) {
return countSqlParser.getSmartCountSql(boundSql.getSql(), countColumn);
}
//没设置 就走这里 ###
return countSqlParser.getSmartCountSql(boundSql.getSql());
}
CountSqlParser#getSmartCountSql
public String getSmartCountSql(String sql) {
return getSmartCountSql(sql, "0");
}
/**
* 获取智能的countSql
*
* @param sql
* @param countColumn 列名,默认 0
* @return
*/
public String getSmartCountSql(String sql, String countColumn) {
//解析SQL
Statement stmt = null;
//特殊sql不需要去掉order by时,使用注释前缀
if(sql.indexOf(KEEP_ORDERBY) >= 0){
return getSimpleCountSql(sql, countColumn);
}
try {
//sql解析 会得到查询的字段 存储在withItemsList里面
stmt = CCJSqlParserUtil.parse(sql);
} catch (Throwable e) {
//无法解析的用一般方法返回count语句 ###
return getSimpleCountSql(sql, countColumn);
}
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, countColumn);
}
//处理with-去order by
processWithItemsList(select.getWithItemsList());
//处理为count查询 ###
sqlToCount(select, countColumn);
String result = select.toString();
return result;
}
- 一般的count语句
getSimpleCountSql
public String getSimpleCountSql(final String sql, String name) {
StringBuilder stringBuilder = new StringBuilder(sql.length() + 40);
stringBuilder.append("select count(");
stringBuilder.append(name);
stringBuilder.append(") from ( \n");
stringBuilder.append(sql);
stringBuilder.append("\n ) tmp_count");
return stringBuilder.toString();
}
- 替换查询列的count语句
sqlToCount
//只贴这几行了
// 是否能简化count查询
List<SelectItem> COUNT_ITEM = new ArrayList<SelectItem>();
//name 默认“0” 或者指定的字段
COUNT_ITEM.add(new SelectExpressionItem(new Column("count(" + name +")")));
if (selectBody instanceof PlainSelect && isSimpleCount((PlainSelect) selectBody)) {
//把解析出来的查询列字段替换成 count(0)了
((PlainSelect) selectBody).setSelectItems(COUNT_ITEM);
}
- 到这里第一步完成了count的查询了,贴下count后续的操作
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.getPageSizeZero() != null) {
//PageSizeZero=false&&pageSize<=0
if (!page.getPageSizeZero() && page.getPageSize() <= 0) {
return false;
}
//PageSizeZero=true&&pageSize<0 返回 false,只有>=0才需要执行后续的
else if (page.getPageSizeZero() && page.getPageSize() < 0) {
return false;
}
}
//页码>0 && 开始行数<总行数即可,不需要考虑 pageSize(上面的 if 已经处理不符合要求的值了)
return page.getPageNum() > 0 && count > page.getStartRow();
}
2.6 pageQuery原始sql查询阶段
- 再次回调
PageInterceptor#intercept找到可以list查询pageQuery
//ExecutorUtil#pageQuery
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;
//处理参数对象
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));
}
//对 boundSql 的拦截处理
if (dialect instanceof BoundSqlInterceptor.Chain) {
pageBoundSql = ((BoundSqlInterceptor.Chain) dialect).doBoundSql(BoundSqlInterceptor.Type.PAGE_SQL, pageBoundSql, pageKey);
}
//执行分页查询
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
} else {
//不执行分页的情况下,也不执行内存分页
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
}
}
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);
}
//如果只支持order by 就不用处理limit了
if (page.isOrderByOnly()) {
return sql;
}
//该类实现了Dialect 这里是委派模式了 到各种数据库方言类
//这里是mysql
return getPageSql(sql, page, pageKey);
}
MySqlDialect类,共两个方法
//处理参数
@Override
public Object processPageParameter(MappedStatement ms, Map<String, Object> paramMap, Page page, BoundSql boundSql, CacheKey pageKey) {
//参数添加 页码
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());
//如果开头是0 可以省略第一个参数 名称和参数里面的保持一致
if (page.getStartRow() == 0) {
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, int.class).build());
} else {
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_FIRST, long.class).build());
newParameterMappings.add(new ParameterMapping.Builder(ms.getConfiguration(), PAGEPARAMETER_SECOND, int.class).build());
}
MetaObject metaObject = MetaObjectUtil.forObject(boundSql);
metaObject.setValue("parameterMappings", newParameterMappings);
}
return paramMap;
}
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
//这里就是动态添加limit了
if (page.getStartRow() == 0) {
sqlBuilder.append("\n LIMIT ? ");
} else {
sqlBuilder.append("\n LIMIT ?, ? ");
}
return sqlBuilder.toString();
}
这里limit的sql已经添加完了,后面就是直接调用执行了,整个流程到这里就结束了。
3.总结
实现了mybatis插件的pagehelper,通过本地线程变量执行了count总数查询,然后动态修改了sql,达到分页的一个效果。
这里记录一下值得注意的几个点:
page==0 || pageSize==0只会执行count查询,分页查询不会查询
//Page 中计算起止行号
private void calculateStartAndEndRow() {
//pageNum=0 那么 startRow=0
this.startRow = this.pageNum > 0 ? (this.pageNum - 1) * this.pageSize : 0;
//0 + pageSize * 0 = 0
this.endRow = this.startRow + this.pageSize * (this.pageNum > 0 ? 1 : 0);
}
//在count查询完成后 AbstractHelperDialect#afterCount方法中
//pageSizeZero 默认false 就是pageSize==0 就不继续查分页
if (!page.getPageSizeZero() && page.getPageSize() <= 0) {
return false;
}
//如果设置pageSize为0时也继续查询分页数据 那么要count结果需>=0才继续
else if (page.getPageSizeZero() && page.getPageSize() < 0) {
return false;
}
PageInterceptor#intercept找到最后返回。
return dialect.afterPage(resultList, parameter, rowBounds);
这里是没有传递参数Invocation invocation过去的,也就是查询链的流程到这里就截止了
基于springboot的自动装配机制,先扫描启动类目录下的class类,再是自动装配类EnableAutoConfiguration配置下的类初始化,手写开发的查询插件先初始化PageInterceptor分页插件,在InterceptorChain#pluginAll最后封装的是PageInterceptor分页插件,然后分页最终执行的返回并不是invocation.proceed(),也就是手写的查询插件会失效掉。
以上就是本章的全部内容了。
上一篇:mybatis第六话 - mybatis插件篇之pagehelper的使用 下一篇:mybatis第八话 - mybaits之ParameterHandler参数处理源码分析
旧书不厌百回读,熟读精思子自知