Mybatis逻辑分页与物理分页PageHelper使用解析
作者:原来是小袁呐
这篇文章主要为大家介绍了Mybatis逻辑分页与物理分页PageHelper使用解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
一、逻辑分页
理解:数据库一次性取出全部数据存储到List集合中,再根据工具类获取指定返回的数据,如下是通过stream流实现
PageUtils
package com.example.segmentfaulttest0.utils; import lombok.Data; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; /** * @description:分页工具类 * @author: 袁凯 * @time: 2023/12/14 19:36 */ @Data public class PageUtils<E extends Serializable> implements Serializable { private static final long serialVersionUID = 1L; /** * 总记录数 */ private int totalCount; /** * 每页记录数 */ private int pageSize; /** * 总页数 */ private int totalPage; /** * 当前页数 */ private int currPage; /** * 列表数据 */ private List<E> list; /** * * @param pageSize 每页记录数 * @param currPage 当前页数 * @param totalList 总记录列表 */ public PageUtils(int pageSize, int currPage, List<E> totalList) { this.totalCount = totalList.size(); this.pageSize = pageSize; this.totalPage = (int) Math.ceil((double) totalCount / pageSize); this.currPage = currPage; this.list = this.currPage >= 1 ? totalList.stream().skip((long) (currPage - 1) * pageSize).limit(pageSize).collect(Collectors.toList()) : new ArrayList<>(); } }
二、物理分页(PageHelper)
1.注意事项
①PageHelper的原理是通过拦截器实现的,他会先发送一个计数SQL语句,再使用limit进行查询。比如在一对多的查询中,我们有时候是根据主表进行分页的,比如说主表有3条,从表有7条,这时候可能出现分页total数量为7,这时候我们可以使用嵌套查询代替联表查询使分页结果准确
②有时候我们需要将查询出来的数据转换为VO对象,但会出现total一直为List.size()的问题,而不是总数量,这是由于我们查出来的并不是ArrayList对象,而是Page对象,其中封装了部分参数,当调用PageInfo的构造方法时,他并不会进入正常的流程,为了解决这个问题,我们需要手动将total传递给新的PageInfo对象,如下
@GetMapping("/select") public TableDataInfo selectSelectiveLike() { startPage(); List<Vrt> vrtList = vrtService.selectSelectiveLike(new Vrt()); PageInfo<Vrt> pageInfo = new PageInfo<>(vrtList); List<VrtVo> vrtVos = new ArrayList<>(); for (Vrt vrt : vrtList) { VrtVo vrtVo = new VrtVo(); BeanUtils.copyProperties(vrt, vrtVo); List<VrtPhone> vrtPhoneList = vrt.getVrtPhoneList(); if (vrtPhoneList != null && !vrtPhoneList.isEmpty()) { vrtVo.setNum(vrtPhoneList.size()); } else { vrtVo.setNum(0); } vrtVos.add(vrtVo); } // 1.由于PageInfo中参数很多,有时候并不需要,因此自定义了一个TableDataInfo对象用于封装参数 // 2.我们在这里手动将PageInfo的值传递给VO的分页对象即可 TableDataInfo tableDataInfo = new TableDataInfo(); tableDataInfo.setCode(HttpStatus.SUCCESS); tableDataInfo.setRows(vrtVos); tableDataInfo.setMsg("查询成功"); tableDataInfo.setTotal(pageInfo.getTotal()); return tableDataInfo; }
2.mybatis简要插件实现
插件类
package com.example.segmentfaulttest0.Interceptor; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.resultset.ResultSetHandler; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.*; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import java.sql.Statement; import java.util.Properties; /** * @description:自定义插件,用于拦截mybatis的query方法 * @author: 袁凯 * @time: 2023/12/18 16:53 */ //mybatis的拦截器注解以及签名注解,用于需要拦截的类名,方法名,参数类型 @Intercepts({//注意看这个大花括号,也就这说这里可以定义多个@Signature对多个地方拦截,都用这个拦截器 @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) //@Intercepts({ // @Signature( // type = Executor.class, // method = "update", // args = {MappedStatement.class, Object.class}), //}) public class MyPlugin implements Interceptor { public MyPlugin() { System.out.println("myplugin"); } @Override public Object intercept(Invocation invocation) throws Throwable { //拦截成功,do something System.out.println("do something"); return invocation.proceed(); } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); //将被拦截对象生成代理对象 } /** * 用于获取pom.xml中property标签中写入的属性 * @param properties */ @Override public void setProperties(Properties properties) { Interceptor.super.setProperties(properties); } }
配置类
package com.example.segmentfaulttest0.config; import com.example.segmentfaulttest0.Interceptor.MyPlugin; import org.apache.ibatis.session.SqlSessionFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.List; /** * @description:Mybatis相关配置 * @author: 袁凯 * @time: 2023/12/18 17:39 */ @Configuration public class MybatisConfig { @Autowired private List<SqlSessionFactory> sqlSessionFactoryList; @Bean public void myPlugin() { for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) { org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration(); // 最后添加的会更早执行 configuration.addInterceptor(new MyPlugin()); } } }
mybatis可拦截的类
对象 | 作用 |
---|---|
StatementHandler(语句处理器) | 负责处理 SQL 语句的预编译、参数设置等工作,其中最核心的工作是创建 JDBC 中的 Statement 对象,并为 SQL 语句绑定参数。 |
ParameterHandler(参数处理器) | 用于处理 SQL 语句中的参数,负责为 SQL 语句中的参数设置值 |
Executor(执行器) | 负责执行由用户发起的对数据库的增删改查操作 |
ResultSetHandler(结果集处理器) | 负责处理 SQL 执行后的结果集,将结果集转换成用户需要的 Java 对象 |
3.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 {
表列 A | 表列 B |
---|---|
MappedStatement var1 | 表示当前执行的SQL的映射配置信息,包括BoundSql对象 |
Object var2 | 表示传递给SQL的参数对象,可以是单个参数,也可以是一个Map或POJO |
RowBounds var3 | 用于控制结果集偏移量和限制数量,即分页查询时的偏移量和限制返回的行数 |
ResultHandler var4 | 负责处理 SQL 执行后的结果集,将结果集转换成用户需要的 Java 对象 |
CacheKey var5 | MyBatis的缓存机制中使用的缓存键,可以用于缓存查询结果 |
BoundSql var6 | 表示包含了SQL语句和对应参数映射信息的BoundSql对象,可以用于访问SQL语句及其参数 |
intercept方法源码解析
public Object intercept(Invocation invocation) throws Throwable { try { // 1.根据重载的拦截query方法不同,获取不同的对象以及缓存key 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) { boundSql = ms.getBoundSql(parameter); cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql); } else { cacheKey = (CacheKey)args[4]; boundSql = (BoundSql)args[5]; } // 2.mybatis对不同的数据库进行方言相关的检查 this.checkDialectExists(); if (this.dialect instanceof BoundSqlInterceptor.Chain) { boundSql = ((BoundSqlInterceptor.Chain)this.dialect).doBoundSql(Type.ORIGINAL, boundSql, cacheKey); } List resultList; if (!this.dialect.skip(ms, parameter, rowBounds)) { this.debugStackTraceLog(); if (this.dialect.beforeCount(ms, parameter, rowBounds)) { // 3.执行一条sql,select count(*) from xxx获取总条数(根据原语句决定) Long count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql); if (!this.dialect.afterCount(count, parameter, rowBounds)) { Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds); return var12; } } // 4.根据分页参数执行语句,在原语句的基础上添加了limit ?,? resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey); } else { resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } Object var16 = this.dialect.afterPage(resultList, parameter, rowBounds); return var16; } finally { if (this.dialect != null) { this.dialect.afterAll(); } } }
PageHelper类中startPage(pageNum,pageSize)的作用
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page<E> page = new Page(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); // 1.使用threadlocal进行线程存储,为每个线程保存每个 分页参数(当前页、页数) Page<E> oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page; }
拦截器如何调用threadlocal里面的分页参数
在上面的intercept方法第四点中,他会进入ExecutorUtil的方法
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)) { return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql); } else { parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey); String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey); BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter); Map<String, Object> additionalParameters = getAdditionalParameter(boundSql); Iterator var12 = additionalParameters.keySet().iterator(); while(var12.hasNext()) { String key = (String)var12.next(); pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key)); } if (dialect instanceof BoundSqlInterceptor.Chain) { // 1.他会再次调用PageHelper类里面的threadlocal,并获取里面的分页参数 pageBoundSql = ((BoundSqlInterceptor.Chain)dialect).doBoundSql(Type.PAGE_SQL, pageBoundSql, cacheKey); } return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql); } }
// 回到pageHelper类中,他会将分页参数封装成Page对象,再放入threadlocal中 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 = this.pageBoundSqlInterceptors != null ? this.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((BoundSqlInterceptor.Chain)chain); } } return ((BoundSqlInterceptor.Chain)chain).doBoundSql(type, boundSql, cacheKey); }
Page对象(实际上返回的对象)与PageInfo对象(我们最终使用的对象)
//1.集成了ArrayList类,用于封装分页信息以及封装查询出来的结果 public class Page<E> extends ArrayList<E>
//1.根据传入的Page对象,获取其中的参数,如total public class PageInfo<T> extends PageSerializable<T> {
总结流程
PageHelper开启分页->将分页参数封装到Page对象中,使用threadlocal存储->PageInterceptor拦截器对方法进行一次拦截(清除threadlocal里面的分页参数)->在拦截器中,分别使用两条SQL语句获取total以及分页后的数据(count和limit),并将信息封装给Page对象->新建PageInfo对象,将Page对象传入,PageInfo对象里面就包含了分页数据及参数
以上就是Mybatis逻辑分页与物理分页PageHelper使用解析的详细内容,更多关于Mybatis PageHelper分页的资料请关注脚本之家其它相关文章!