Mybatis系列插件(一)-Mybatis-PageHelper分页插件
前言
本篇主要讲述Mybatis-PageHelper分页插件使用和其底层原理。
Spring 集成Mybatis-PageHelper
在Mybatis集成篇(一)篇幅中讲了Spring集成Mybatis,在这个基础上,我们把Mybatis-PageHelper也集成进去
案例:
在原有的pom文件中增加Mybatis-PageHelper依赖
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
在原有的MybatisConfig文件中增加几行代码
MybatisConfig
@Bean
public SqlSessionFactory getSqlSessionFactory() throws Exception {
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setLogImpl(StdOutImpl.class);
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setConfiguration(configuration);
// 分页拦截器
PageInterceptor pageInterceptor = new PageInterceptor();
Properties properties = new Properties();
// 这里的需要根据数据库类型设置
properties.setProperty("helperDialect","mysql");
pageInterceptor.setProperties(properties);
// 添加拦截器
configuration.addInterceptor(pageInterceptor);
return factoryBean.getObject();
}
Main类改造
public class Main {
public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MybatisConfig.class);
UserMapper userMapper = applicationContext.getBean(UserMapper.class);
// PageHelper打开方式 start
PageHelper.startPage(1,10);
List<User> users = userMapper.selectUser(1);
PageInfo<User> userPageInfo = new PageInfo<>(users);
// PageHelper打开方式 end
userPageInfo.getList().forEach(System.out::println);
users.forEach(System.out::println);
}
}
结果:
Springboot 集成Mybatis-Pagehelper
在Mybatis集成篇(而)篇幅中讲了Spring集成Mybatis,在这个基础上,我们把Mybatis-PageHelper也集成进去
案例:
在原有的pom文件中增加Mybatis-PageHelper依赖
<!--分页插件-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>2.1.0</version>
</dependency>
ApplicationTest.test2()改造
@Test
public void test2() {
PageHelper.startPage(1,10);
List<User> users = userMapper.selectList(new LambdaQueryWrapper<User>());
PageInfo pageInfo = new PageInfo(users);
pageInfo.getList().forEach(System.out::println);
}
结果:
原理
从第一个案例的可以看出, configuration.addInterceptor(pageInterceptor)才使得分页插件功能生效。同理,从第二个案例分析自动转配类PageHelperAutoConfiguration
也是增加了分页插件的PageInterceptor是,所以我们主要的入口是PageInterceptor类
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}),
}
)
public class PageInterceptor implements Interceptor {
private static final Log log = LogFactory.getLog(PageInterceptor.class);
private static boolean debug = false;
protected Cache<String, MappedStatement> msCountMap = null;
protected CountMsIdGen countMsIdGen = CountMsIdGen.DEFAULT;
private volatile Dialect dialect;
private String countSuffix = "_COUNT";
private String default_dialect_class = "com.github.pagehelper.PageHelper";
public PageInterceptor() {
String bannerEnabled = System.getProperty("pagehelper.banner");
if (StringUtil.isEmpty(bannerEnabled)) {
bannerEnabled = System.getenv("PAGEHELPER_BANNER");
}
//默认 TRUE
if (StringUtil.isEmpty(bannerEnabled) || Boolean.parseBoolean(bannerEnabled)) {
log.debug("\n\n" +
",------. ,--. ,--. ,--. \n" +
"| .--. ' ,--,--. ,---. ,---. | '--' | ,---. | | ,---. ,---. ,--.--. \n" +
"| '--' | ' ,-. | | .-. | | .-. : | .--. | | .-. : | | | .-. | | .-. : | .--' \n" +
"| | --' \\ '-' | ' '-' ' \\ --. | | | | \\ --. | | | '-' ' \\ --. | | \n" +
"`--' `--`--' .`- / `----' `--' `--' `----' `--' | |-' `----' `--' \n" +
" `---' `--' is intercepting.\n");
}
}
public static boolean isDebug() {
return debug;
}
/**
* 输出启用分页方法时的调用堆栈信息
*/
protected void debugStackTraceLog() {
if (isDebug()) {
Page<Object> page = PageMethod.getLocalPage();
log.debug(page.getStackTrace());
}
}
@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;
//调用方法判断是否需要进行分页,如果不需要,直接返回结果
// dialect默认是PageHelper,PageHelper类型会根据jdbc的url找到对应的处理类
// 例如jdbc:mysql://116.198.234.105:3306/mybatis?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true这个url,会根据url中:xx:这样的标识找到对应的处理类,类似例子中,会找到MySqlDialect处理类
if (!dialect.skip(ms, parameter, rowBounds)) {
//开启debug时,输出触发当前分页执行时的PageHelper调用堆栈
// 如果和当前调用堆栈不一致,说明在启用分页后没有消费,当前线程再次执行时消费,调用堆栈显示的方法使用不安全
debugStackTraceLog();
Future<Long> countFuture = null;
//判断是否需要进行 count 查询
if (dialect.beforeCount(ms, parameter, rowBounds)) {
if (dialect.isAsyncCount()) {
countFuture = asyncCount(ms, boundSql, parameter, rowBounds);
} else {
//查询总数
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);
}
}
}
resultList = ExecutorUtil.pageQuery(dialect, executor,
ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
if (countFuture != null) {
Long count = countFuture.get();
dialect.afterCount(count, parameter, rowBounds);
}
} else {
//rowBounds用参数值,不使用分页插件处理时,仍然支持默认的内存分页
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
return dialect.afterPage(resultList, parameter, rowBounds);
} finally {
if (dialect != null) {
dialect.afterAll();
}
}
}
/**
* 异步查询总数
*/
private Future<Long> asyncCount(MappedStatement ms, BoundSql boundSql, Object parameter, RowBounds rowBounds) {
Configuration configuration = ms.getConfiguration();
//异步不能复用 BoundSql,因为分页使用时会添加分页参数,这里需要复制一个新的
BoundSql countBoundSql = new BoundSql(configuration, boundSql.getSql(), new ArrayList<>(boundSql.getParameterMappings()), parameter);
Map<String, Object> additionalParameter = ExecutorUtil.getAdditionalParameter(boundSql);
if (additionalParameter != null){
for (String key : additionalParameter.keySet()) {
countBoundSql.setAdditionalParameter(key, additionalParameter.get(key));
}
}
//异步想要起作用需要新的数据库连接,需要独立的事务,创建新的Executor,因此异步查询只适合在独立查询中使用,如果混合增删改操作,不能开启异步
Environment environment = configuration.getEnvironment();
TransactionFactory transactionFactory = null;
if (environment == null || environment.getTransactionFactory() == null) {
transactionFactory = new ManagedTransactionFactory();
} else {
transactionFactory = environment.getTransactionFactory();
}
//创建新的事务
Transaction tx = transactionFactory.newTransaction(environment.getDataSource(), null, false);
//使用新的 Executor 执行 count 查询,这里没有加载拦截器,避免递归死循环
Executor countExecutor = new CachingExecutor(new SimpleExecutor(configuration, tx));
return dialect.asyncCountTask(() -> {
try {
return count(countExecutor, ms, parameter, rowBounds, null, countBoundSql);
} finally {
tx.close();
}
});
}
/**
* Spring bean 方式配置时,如果没有配置属性就不会执行下面的 setProperties 方法,就不会初始化
* <p>
* 因此这里会出现 null 的情况 fixed #26
*/
private void checkDialectExists() {
if (dialect == null) {
synchronized (default_dialect_class) {
if (dialect == null) {
setProperties(new Properties());
}
}
}
}
private Long count(Executor executor, MappedStatement ms, Object parameter,
RowBounds rowBounds, ResultHandler resultHandler,
BoundSql boundSql) throws SQLException {
String countMsId = countMsIdGen.genCountMsId(ms, parameter, boundSql, countSuffix);
Long count;
//先判断是否存在手写的 count 查询
MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
if (countMs != null) {
count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
} else {
if (msCountMap != null) {
countMs = msCountMap.get(countMsId);
}
//自动创建
if (countMs == null) {
//根据当前的 ms 创建一个返回值为 Long 类型的 ms
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;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
//缓存 count ms
msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties);
String dialectClass = properties.getProperty("dialect");
if (StringUtil.isEmpty(dialectClass)) {
dialectClass = default_dialect_class;
}
Dialect tempDialect = ClassUtil.newInstance(dialectClass, properties);
tempDialect.setProperties(properties);
String countSuffix = properties.getProperty("countSuffix");
if (StringUtil.isNotEmpty(countSuffix)) {
this.countSuffix = countSuffix;
}
// debug模式,用于排查不安全分页调用
debug = Boolean.parseBoolean(properties.getProperty("debug"));
// 通过 countMsId 配置自定义类
String countMsIdGenClass = properties.getProperty("countMsIdGen");
if (StringUtil.isNotEmpty(countMsIdGenClass)) {
countMsIdGen = ClassUtil.newInstance(countMsIdGenClass, properties);
}
// 初始化完成后再设置值,保证 dialect 完成初始化
dialect = tempDialect;
}
}
PageHelper
/*
* The MIT License (MIT)
*
* Copyright (c) 2014-2023 abel533@gmail.com
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package com.github.pagehelper;
import com.github.pagehelper.dialect.AbstractHelperDialect;
import com.github.pagehelper.page.PageAutoDialect;
import com.github.pagehelper.page.PageBoundSqlInterceptors;
import com.github.pagehelper.page.PageMethod;
import com.github.pagehelper.page.PageParams;
import com.github.pagehelper.parser.CountSqlParser;
import com.github.pagehelper.util.StringUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.session.RowBounds;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.Future;
/**
* Mybatis - 通用分页拦截器<br/>
* 项目地址 : http://git.oschina.net/free/Mybatis_PageHelper
*
* @author liuzh/abel533/isea533
* @version 5.0.0
*/
public class PageHelper extends PageMethod implements Dialect, BoundSqlInterceptor.Chain {
private PageParams pageParams;
private PageAutoDialect autoDialect;
private PageBoundSqlInterceptors pageBoundSqlInterceptors;
private ForkJoinPool asyncCountService;
@Override
public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
Page page = pageParams.getPage(parameterObject, rowBounds);
if (page == null) {
return true;
} else {
//设置默认的 count 列
if (StringUtil.isEmpty(page.getCountColumn())) {
page.setCountColumn(pageParams.getCountColumn());
}
//设置默认的异步 count 设置
if (page.getAsyncCount() == null) {
page.setAsyncCount(pageParams.isAsyncCount());
}
autoDialect.initDelegateDialect(ms, page.getDialectClass());
return false;
}
}
@Override
public boolean isAsyncCount() {
return getLocalPage().asyncCount();
}
@Override
public <T> Future<T> asyncCountTask(Callable<T> task) {
//异步执行时需要将ThreadLocal值传递,否则会找不到
AbstractHelperDialect dialectThreadLocal = autoDialect.getDialectThreadLocal();
Page<Object> localPage = getLocalPage();
String countId = UUID.randomUUID().toString();
return asyncCountService.submit(() -> {
try {
//设置 ThreadLocal
autoDialect.setDialectThreadLocal(dialectThreadLocal);
setLocalPage(localPage);
return task.call();
} finally {
autoDialect.clearDelegate();
clearPage();
}
});
}
@Override
public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
return autoDialect.getDelegate().beforeCount(ms, parameterObject, rowBounds);
}
@Override
public String getCountSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey countKey) {
return autoDialect.getDelegate().getCountSql(ms, boundSql, parameterObject, rowBounds, countKey);
}
@Override
public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) {
return autoDialect.getDelegate().afterCount(count, parameterObject, rowBounds);
}
@Override
public Object processParameterObject(MappedStatement ms, Object parameterObject, BoundSql boundSql, CacheKey pageKey) {
return autoDialect.getDelegate().processParameterObject(ms, parameterObject, boundSql, pageKey);
}
@Override
public boolean beforePage(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
return autoDialect.getDelegate().beforePage(ms, parameterObject, rowBounds);
}
@Override
public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
return autoDialect.getDelegate().getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);
}
public String getPageSql(String sql, Page page, RowBounds rowBounds, CacheKey pageKey) {
return autoDialect.getDelegate().getPageSql(sql, page, pageKey);
}
@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;
}
@Override
public void afterAll() {
//这个方法即使不分页也会被执行,所以要判断 null
AbstractHelperDialect delegate = autoDialect.getDelegate();
if (delegate != null) {
delegate.afterAll();
autoDialect.clearDelegate();
}
clearPage();
}
@Override
public BoundSql doBoundSql(BoundSqlInterceptor.Type type, BoundSql boundSql, CacheKey cacheKey) {
Page<Object> localPage = getLocalPage();
BoundSqlInterceptor.Chain chain = localPage != null ? localPage.getChain() : null;
if (chain == null) {
BoundSqlInterceptor boundSqlInterceptor = localPage != null ? localPage.getBoundSqlInterceptor() : null;
BoundSqlInterceptor.Chain defaultChain = pageBoundSqlInterceptors != null ? pageBoundSqlInterceptors.getChain() : null;
if (boundSqlInterceptor != null) {
chain = new BoundSqlInterceptorChain(defaultChain, Arrays.asList(boundSqlInterceptor));
} else if (defaultChain != null) {
chain = defaultChain;
}
if (chain == null) {
chain = DO_NOTHING;
}
if (localPage != null) {
localPage.setChain(chain);
}
}
return chain.doBoundSql(type, boundSql, cacheKey);
}
@Override
public void setProperties(Properties properties) {
setStaticProperties(properties);
pageParams = new PageParams();
autoDialect = new PageAutoDialect();
pageBoundSqlInterceptors = new PageBoundSqlInterceptors();
pageParams.setProperties(properties);
autoDialect.setProperties(properties);
pageBoundSqlInterceptors.setProperties(properties);
//20180902新增 aggregateFunctions, 允许手动添加聚合函数(影响行数)
CountSqlParser.addAggregateFunctions(properties.getProperty("aggregateFunctions"));
// 异步 asyncCountService 并发度设置,这里默认为应用可用的处理器核心数 * 2,更合理的值应该综合考虑数据库服务器的处理能力
int asyncCountParallelism = Integer.parseInt(properties.getProperty("asyncCountParallelism",
"" + (Runtime.getRuntime().availableProcessors() * 2)));
asyncCountService = new ForkJoinPool(asyncCountParallelism,
pool -> {
final ForkJoinWorkerThread worker = ForkJoinPool.defaultForkJoinWorkerThreadFactory.newThread(pool);
worker.setName("pagehelper-async-count-" + worker.getPoolIndex());
return worker;
}, null, true);
}
}
那本篇到这里就结束了。
原文地址:https://blog.csdn.net/YaYicho/article/details/144329031
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!