zoukankan      html  css  js  c++  java
  • 若依项目中如何分页的-浅析

    使用的插件有

    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper-spring-boot-starter</artifactId>
      <version>1.2.13</version>
    </dependency>
    
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.3.1</version>
    </dependency>
    

    控制器中使用下面语句就能分页。

      startPage();
      List<SysUser> list = userService.selectUserList(user);
      return getDataTable(list);
    

    方法startPage,有什么用呢?

    protected void startPage()
    {
        PageDomain pageDomain = TableSupport.buildPageRequest();
        Integer pageNum = pageDomain.getPageNum();
        Integer pageSize = pageDomain.getPageSize();
        if (StringUtils.isNotNull(pageNum) && StringUtils.isNotNull(pageSize))
        {
            String orderBy = pageDomain.getOrderBy();
            PageHelper.startPage(pageNum, pageSize, orderBy);
        }
    }
    

    startPage首先将前台传递过来的分页信息记录下来。

    public static PageDomain getPageDomain()
    {
        PageDomain pageDomain = new PageDomain();
        pageDomain.setPageNum(ServletUtils.getParameterToInt(Constants.PAGE_NUM));
        pageDomain.setPageSize(ServletUtils.getParameterToInt(Constants.PAGE_SIZE));
        pageDomain.setOrderByColumn(ServletUtils.getParameter(Constants.ORDER_BY_COLUMN));
        pageDomain.setIsAsc(ServletUtils.getParameter(Constants.IS_ASC));
        return pageDomain;
    }
    
    public static PageDomain buildPageRequest()
    {
        return getPageDomain();
    }
    

    那么PageHelper.startPage(pageNum, pageSize, orderBy)是什么呢?

    /**
      * 开始分页
      *
      * @param pageNum  页码
      * @param pageSize 每页显示数量
      * @param orderBy  排序
      */
    public static <E> Page<E> startPage(int pageNum, int pageSize, String orderBy) {
        Page<E> page = startPage(pageNum, pageSize);
        page.setOrderBy(orderBy);
        return page;
    }
    ···
    /**
      * 开始分页
      *
      * @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;
    }
    

    其中getLocalPage()和setLocalPage();很奇怪:

    protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal<Page>();
    protected static boolean DEFAULT_COUNT = true;
    
    /**
      * 设置 Page 参数
      *
      * @param page
      */
    protected static void setLocalPage(Page page) {
        LOCAL_PAGE.set(page);
    }
    
    /**
      * 获取 Page 参数
      *
      * @return
      */
    public static <T> Page<T> getLocalPage() {
        return LOCAL_PAGE.get();
    }
    

    原来setLocalPage是将page保存到本地线程变量里面。那么有赋值,就该有取值,不然就没什么用了,对吧,但是在哪里用呢?先跳过这里。回到selectUserList。
    执行完startPage,就执行了mybatis的sql语句:

    <select id="selectUserList" parameterType="SysUser" resultMap="SysUserResult">
    select u.user_id, u.dept_id, u.login_name, u.user_name, u.email, u.phonenumber, u.password, u.sex, u.avatar, u.salt, u.status, u.order_num, u.del_flag, u.login_ip, u.login_date, u.create_by, u.create_time, u.update_time, u.remark
    from sys_user u
    where u.del_flag = '0'
    group by u.user_id
    order by u.order_num, u.user_name, d.order_num
    </select>
    

    就是这么简单的sql语句,为甚页面会分页呢?难道有地方进行了拦截处理?

    debug发现进入com.baomidou.mybatisplus.core.override的MybatisMapperProxy类中

    @Override
      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
          try {
              if (Object.class.equals(method.getDeclaringClass())) {
                  return method.invoke(this, args);
              } else if (method.isDefault()) {
                  if (privateLookupInMethod == null) {
                      return invokeDefaultMethodJava8(proxy, method, args);
                  } else {
                      return invokeDefaultMethodJava9(proxy, method, args);
                  }
              }
          } catch (Throwable t) {
              throw ExceptionUtil.unwrapThrowable(t);
          }
          final MybatisMapperMethod mapperMethod = cachedMapperMethod(method);
          return mapperMethod.execute(sqlSession, args);
      }
    

    最后执行execute方法,接着进入MybatisMapperMethod类中:

    public Object execute(SqlSession sqlSession, Object[] args) {
      Object result;
      switch (command.getType()) {
          case INSERT: {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = rowCountResult(sqlSession.insert(command.getName(), param));
              break;
          }
          case UPDATE: {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = rowCountResult(sqlSession.update(command.getName(), param));
              break;
          }
          case DELETE: {
              Object param = method.convertArgsToSqlCommandParam(args);
              result = rowCountResult(sqlSession.delete(command.getName(), param));
              break;
          }
          case SELECT:
              if (method.returnsVoid() && method.hasResultHandler()) {
                  executeWithResultHandler(sqlSession, args);
                  result = null;
              } else if (method.returnsMany()) {
                  result = executeForMany(sqlSession, args);
              } else if (method.returnsMap()) {
                  result = executeForMap(sqlSession, args);
              } else if (method.returnsCursor()) {
                  result = executeForCursor(sqlSession, args);
              } else {
                  Object param = method.convertArgsToSqlCommandParam(args);
                  if (IPage.class.isAssignableFrom(method.getReturnType())) {
                      assert args != null;
                      IPage<?> page = null;
                      for (Object arg : args) {
                          if (arg instanceof IPage) {
                              page = (IPage) arg;
                              break;
                          }
                      }
                      assert page != null;
                      result = executeForIPage(sqlSession, args);
                      if (result instanceof PageList) {
                          PageList pageList = (PageList) result;
                          page.setRecords(pageList.getRecords());
                          page.setTotal(pageList.getTotal());
                          result = page;
                      } else {
                          List list = (List<Object>) result;
                          result = page.setRecords(list);
                      }
                  } else {
                      result = sqlSession.selectOne(command.getName(), param);
                      if (method.returnsOptional()
                          && (result == null || !method.getReturnType().equals(result.getClass()))) {
                          result = Optional.ofNullable(result);
                      }
                  }
              }
              break;
          case FLUSH:
              result = sqlSession.flushStatements();
              break;
          default:
              throw new BindingException("Unknown execution method for: " + command.getName());
      }
      if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
          throw new BindingException("Mapper method '" + command.getName()
              + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
      }
      return result;
    }
    

    执行的查询多条的select,所以到了 result = executeForMany(sqlSession, args)
    然后是SqlSessionTemplate和DefaultSqlSession
    最后可以看到代码进入了org.apache.ibatis.plugin.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)) {
            return interceptor.intercept(new Invocation(target, method, args));
          }
          return method.invoke(target, args);
        } catch (Exception e) {
          throw ExceptionUtil.unwrapThrowable(e);
        }
      }
    

    这下明白了,interceptor是mybatis的拦截器,而com.github.pagehelper.PageInterceptor这个类就实现了interceptor接口,调用其中的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();
    
            List resultList;
            //调用方法判断是否需要进行分页,如果不需要,直接返回结果
            if (!dialect.skip(ms, parameter, rowBounds)) {
                //判断是否需要进行 count 查询
                if (dialect.beforeCount(ms, parameter, rowBounds)) {
                    //查询总数
                    Long count = count(executor, ms, parameter, rowBounds, resultHandler, 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();
            }
        }
    }
    

    ExecutorUtil.pageQuery()来处理分页,但是怎么处理的呢?
    com.github.pagehelper.util.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)) {
            //生成分页的缓存 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));
            }
            //执行分页查询
            return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, pageKey, pageBoundSql);
        } else {
            //不执行分页的情况下,也不执行内存分页
            return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
        }
    }
    

    getPageSql方法:
    com.github.pagehelper.PageHelper

    @Override
      public String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey) {
          return autoDialect.getDelegate().getPageSql(ms, boundSql, parameterObject, rowBounds, pageKey);
      }
    

    然后调用com.github.pagehelper.dialect.AbstractHelperDialect

    @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);
          }
          if (page.isOrderByOnly()) {
              return sql;
          }
          return getPageSql(sql, page, pageKey);
      }
    

    在getPageSql中getLocalPage将ThreadLocal中page变量取出来。
    com.github.pagehelper.dialect.helper.MySqlDialect

    @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(" LIMIT ? ");
        } else {
            sqlBuilder.append(" LIMIT ?, ? ");
        }
        return sqlBuilder.toString();
    }
    

    PageHelper首先将前端传递的参数保存到page这个对象中,接着将page的副本存放入ThreadLoacl中,这样可以保证分页的时候,参数互不影响,接着利用了mybatis提供的拦截器,取得ThreadLocal的值,重新拼装分页SQL,完成分页

  • 相关阅读:
    Appium安装教程
    方法(method)和函数(function)有什么区别?
    FTP两种工作模式:主动模式(Active FTP)和被动模式介绍
    python socket编程介绍
    面向对象基础篇
    python fishc.homework2
    python遇到的问题汇总
    我对 python 面向对象的理解
    深入理解JVM(五)JVM优化策略
    深入理解JVM(四)JVM性能监控与故障处理工具
  • 原文地址:https://www.cnblogs.com/mantishell/p/13674818.html
Copyright © 2011-2022 走看看