活动地址:毕业季·进击的技术er
职场人之内卷人
PageHelper的具体用法还需要我给大家讲吗?算了讲一下得了。用法非常之简单
导入依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>XXXX</version>
</dependency>
在xml中的sql写法 ,很简单不用写 Limi ?,?的。举个栗子巴
<select id="findAll" resultType="com.xxx.xxx.dao.XxxxDao" >
select *from sys_user
</select>
我们在service层 的写法
// pageNum 第几页
// pageSize 每页展示条数
public PageInfo<SysUser> page(Integer pageNum,
Integer pageSize){
PageHelper.startPage(pageNum,pageSize);
List<SysUser> list=SysUserMapper.findAll();
PageInfo<SysUser> sysUserPage = new PageInfo<SysUser>(list);
return sysUserPage;
}
用法的话也可以百度 关键词:PageHelper的用法。
首先我们说一下我们为什么要用 PageHelper
- 帮我们做分页
- 帮我们查询总条数
就聊聊怎么帮我们分页的
- 聊这个之前 首先你需要了解并知道 ThreadLocal 这个线程本地变量。
面试必问-ThreadLocal实现原理-源码分析-面试分析
如果不懂 ThreadLocal 你就先看看 大概需要 5-10分分钟
先说结论
其实巴!PageHelperd 底层帮我们做了 LIMIT ?, ?
请求过来也就相当一个线程过来。要想实现线程之间隔离,每一个线程都有自己的副本。最佳选择 ThreadLocal
mybatis 中的拦截器
废话不多说上源码
PageHelper.startPage 点进去
最终调用的是: 看上去挺简单 就是 对象赋值
/**
* 开始分页
*
* @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);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
//当已经执行过orderBy的时候
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
return page;
}
注意看 :Page oldPage = getLocalPage();、setLocalPage(page);
来学习一下巴 这俩个方法
Page oldPage = getLocalPage(); 你要是学过 ThreadLocal 你一看便知 这里就不在奥术了
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
public static <T> Page<T> getLocalPage() {
return LOCAL_PAGE.get();
}
setLocalPage(page); 先猜一下 别看结论 :往ThreadLocal 赋值 page的
protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
public static <T> Page<T> getLocalPage() {
return LOCAL_PAGE.get();
}
以上的代码分析也是很重要的欧!!!哈哈哈
那个那个~~~ Mybatis拦截器 在PageHelperd源码中如何使用的
注解@Intercepts和接口Interceptor 联合使用 才会生效哈!
@Signature 注解中的属性 聊一下。type :要拦截的类,method:拦截类中的那个方法,args:要处理的 对象 作为参数
@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 {
}
执行代码 断点走起
代码执行结果说明:
算了 我把这个方法都粘贴出来
@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);
}
}
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();
}
}
}
我就不一行一行的讲了 我就 断点到关键代码
- 当前方法 断点放到 resultList = ExecutorUtil.pageQuery(dialect, executor,ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
F7 进到那个Utils 中
代码也粘贴出来 你们呢那边方便看看
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);
}
}
仔细看断点位置 兄弟们
实际数据分析
更进去
getPageSql 代码给你贴出来康康
@Override
public String getPageSql(String sql, Page page, CacheKey pageKey) {
StringBuilder sqlBuilder = new StringBuilder(sql.length() + 14);
sqlBuilder.append(sql);
if (page.getStartRow() == 0) {
sqlBuilder.append("\n LIMIT ? ");
} else {
sqlBuilder.append("\n LIMIT ?, ? ");
}
return sqlBuilder.toString();
}
代码往下走
不懂mybatis的源码的可以康康我的这篇文章
求总条数的那个方法 我在这里就不讲了 思想都是一样饿 就是参数 不同。查询的方法还是一样的
看一下代码截图
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));
}
//对 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);
Long count = (Long) ((List) countResultList).get(0);
return count;
}
这样的话还差最后一个地方没有讲到了 哈哈哈
dialect.afterAll();
dialect.afterAll(); 代码跟景区
分析一下源码
@Override
public void afterAll() {
//这个方法即使不分页也会被执行,所以要判断 null
AbstractHelperDialect delegate = autoDialect.getDelegate();
if (delegate != null) {
// 空方法
delegate.afterAll();
// 移除代理对象的ThreadLocal
autoDialect.clearDelegate();
}
// 移除 当前 page 对象的 ThreadLocal
clearPage();
}
职场人:SteveCode
编写实属不易,看到这里 就三联一手巴! 谢谢各位!
点赞,收藏,分享、关注。算了 ! 能帮到你 是我最大的心愿。
不足的地方 。别忘了指正哈!一块进步
本人公众号:SteveCode
技术分享的,关注我不吃亏。哈哈哈
活动地址:毕业季·进击的技术er