开发者

Mybatis逻辑分页与物理分页PageHelper使用解析

目录
  • 一、逻辑分页
    • PageUtils
  • 二、物理分页(PageHelper)
    • 1.注意事项
    • 2.myBATis简要插件实现
      • 插件类
      • 配置类
      • mybatis可拦截的类
    • 3.PageInterceptor拦截器说明
      • 头部注解及相关对象作用
      • intercept方法源码解析
      • PageHelper类中startPage(pageNum,pageSize)的作用
      • 拦截器如何调用threadlocal里面的分页参数
      • Page对象(实际上返回的对象)与PageInfo对象(我们最终使用的对象)
  • 总结流程

    一、逻辑分页

    理解:数据库一次性取出全部数据存储到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> tot编程客栈alList) {
            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 var5MyBatis的缓存机制中使用的缓存键,可以用于缓存查询结果
    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);
                retjsurn 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.keySetpython().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);
            }
        }
    // 回OTqWrsZ到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分页的资料请关注编程客栈(www.devze.com)其它相关文章!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜