<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          記一次 PageHelper 分頁未生效問題排查

          共 30213字,需瀏覽 61分鐘

           ·

          2021-09-10 14:43

          作者:raledong

          來源:SegmentFault 思否社區(qū)

          問題描述

          最近在項(xiàng)目中使用PageHelper分頁工具+Mybatis實(shí)現(xiàn)分頁查詢邏輯,但是發(fā)現(xiàn)分頁邏輯并沒有生效,代碼片段如下:

          public PageDTO<ChargeMetaDO> pageByParams(SearchChargeReq searchChargeRequest, int pageNo, int pageSize) {
                  PageHelper.startPage(pageNo, pageSize);
                  ChargePOExample example = buildExample(searchChargeRequest);
                  long total = chargeMapper.countByExample(example);
                  List<ChargeMetaDO> chargeMetaDoList = selectByExample(example);
                  return new PageDTO<>(total, chargeMetaDoList);
              }

          這段代碼中傳入了查詢參數(shù)和分頁信息,并返回總頁數(shù)和當(dāng)前頁號(hào)的數(shù)據(jù)。但是實(shí)際執(zhí)行的時(shí)候返回了全部的數(shù)據(jù)。

          修復(fù)方式

          排查的時(shí)候發(fā)現(xiàn),count代碼通過mybatis生成的sql語句中包含了分頁參數(shù),但是select語句卻沒有,因此將查詢數(shù)據(jù)列表的請(qǐng)求放在計(jì)算總數(shù)據(jù)行數(shù)前面即可解決這個(gè)問題。

          public PageDTO<ChargeMetaDO> pageByParams(SearchChargeReq searchChargeRequest, int pageNo, int pageSize) {
                  PageHelper.startPage(pageNo, pageSize);
                  ChargePOExample example = buildExample(searchChargeRequest);
                  // 需要先執(zhí)行分頁查詢,再查詢總數(shù)
                  List<ChargeMetaDO> chargeMetaDoList = selectByExample(example);
                  long total = chargeMapper.countByExample(example);
                  return new PageDTO<>(total, chargeMetaDoList);
              }

          原因分析

          這里閱讀了一下PageHepler,簡單介紹一下PageHelper是如何將分頁信息塞入當(dāng)查詢請(qǐng)求中的。

          在調(diào)用PageHelper.startPage方法后最終會(huì)進(jìn)入這段邏輯:

          protected static final ThreadLocal<Page> LOCAL_PAGE = new ThreadLocal();

              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);
                  Page<E> oldPage = getLocalPage();
                  if (oldPage != null && oldPage.isOrderByOnly()) {
                      page.setOrderBy(oldPage.getOrderBy());
                  }

                  setLocalPage(page);
                  return page;
              }

              protected static void setLocalPage(Page page) {
                  LOCAL_PAGE.set(page);
              }

          這里要關(guān)注setLocalPage這個(gè)方法,這一行代碼將分頁的信息放入了當(dāng)前線程上下文LOCAL_PAGE中,使得后續(xù)的同線程內(nèi)查詢可以從該變量中取到分頁信息。

          那么這個(gè)分頁信息是在哪里被編入到SQL的呢?PageHelper工具實(shí)現(xiàn)了一個(gè)mybatis的攔截器PageInterceptor,在請(qǐng)求經(jīng)過該攔截器時(shí)會(huì)讀取LOCAL_PAGE中的分頁信息并寫入到SQL中。這里通過Dialect接口進(jìn)行了抽象,Dialect接口定義了在經(jīng)過該切面時(shí)的各種行為(如是否跳過,執(zhí)行前操作,執(zhí)行后操作等),并子類PageHelper實(shí)現(xiàn)分頁邏輯。

          public class PageInterceptor implements Interceptor {
              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) {
                          boundSql = ms.getBoundSql(parameter);
                          cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
                      } else {
                          cacheKey = (CacheKey)args[4];
                          boundSql = (BoundSql)args[5];
                      }

                      this.checkDialectExists();
                      List resultList;
                      // 需要跳過該Dialect
                      if (!this.dialect.skip(ms, parameter, rowBounds)) {
                          // 是否要執(zhí)行count操作
                          if (this.dialect.beforeCount(ms, parameter, rowBounds)) {
                              // 計(jì)算數(shù)據(jù)總量
                              Long count = this.count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                              // 判斷是否需要執(zhí)行count后的邏輯
                              if (!this.dialect.afterCount(count, parameter, rowBounds)) {
                                  // 無需執(zhí)行分頁,直接返回
                                  Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);
                                  return var12;
                              }
                          }

                          // 分頁查詢數(shù)據(jù)
                          resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
                      } else {
                          // rowBounds用參數(shù)值,不使用分頁插件處理
                          resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
                      }

                      // 分頁結(jié)束,返回結(jié)果
                      Object var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
                      return var16;
                  } finally {
                      // 分頁操作的收尾工作
                      if (this.dialect != null) {
                          this.dialect.afterAll();
                      }

                  }
              }

               private Long count(Executor executor, MappedStatement ms, Object parameter,
                                 RowBounds rowBounds, ResultHandler resultHandler,
                                 BoundSql boundSql) throws SQLException {
                  String countMsId = ms.getId() + countSuffix;
                  Long count;
                  //先判斷是否存在手寫的 count 查詢
                  MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
                  if (countMs != null) {
                      count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
                  } else {
                      // 優(yōu)先獲取緩存的SQL語句,減少重新生成的成本
                      countMs = msCountMap.get(countMsId);
                      //自動(dòng)創(chuàng)建
                      if (countMs == null) {
                          //拼接count語句
                          countMs = MSUtils.newCountMappedStatement(ms, countMsId);
                          msCountMap.put(countMsId, countMs);
                      }
                      // 執(zhí)行count語句
                      count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler);
                  }
                  return count;
              }
          }

          上文中對(duì)關(guān)鍵行增加了注釋,可以看到intercept方法其實(shí)就是對(duì)Dialect的幾個(gè)方法進(jìn)行編排:

          public interface Dialect {
              /**
               * 跳過 count 和 分頁查詢
               *
               * @param ms              MappedStatement
               * @param parameterObject 方法參數(shù)
               * @param rowBounds       分頁參數(shù)
               * @return true 跳過,返回默認(rèn)查詢結(jié)果,false 執(zhí)行分頁查詢
               */
              boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds);

              /**
               * 執(zhí)行分頁前,返回 true 會(huì)進(jìn)行 count 查詢,false 會(huì)繼續(xù)下面的 beforePage 判斷
               *
               * @param ms              MappedStatement
               * @param parameterObject 方法參數(shù)
               * @param rowBounds       分頁參數(shù)
               * @return
               */
              boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds);


              /**
               * 執(zhí)行完 count 查詢后
               *
               * @param count           查詢結(jié)果總數(shù)
               * @param parameterObject 接口參數(shù)
               * @param rowBounds       分頁參數(shù)
               * @return true 繼續(xù)分頁查詢,false 直接返回
               */
              boolean afterCount(long count, Object parameterObject, RowBounds rowBounds);

              /**
               * 執(zhí)行分頁前,返回 true 會(huì)進(jìn)行分頁查詢,false 會(huì)返回默認(rèn)查詢結(jié)果
               *
               * @param ms              MappedStatement
               * @param parameterObject 方法參數(shù)
               * @param rowBounds       分頁參數(shù)
               * @return
               */
              boolean beforePage(MappedStatement ms, Object parameterObject, RowBounds rowBounds);

              /**
               * 生成分頁查詢 sql
               *
               * @param ms              MappedStatement
               * @param boundSql        綁定 SQL 對(duì)象
               * @param parameterObject 方法參數(shù)
               * @param rowBounds       分頁參數(shù)
               * @param pageKey         分頁緩存 key
               * @return
               */
              String getPageSql(MappedStatement ms, BoundSql boundSql, Object parameterObject, RowBounds rowBounds, CacheKey pageKey);

              /**
               * 分頁查詢后,處理分頁結(jié)果,攔截器中直接 return 該方法的返回值
               *
               * @param pageList        分頁查詢結(jié)果
               * @param parameterObject 方法參數(shù)
               * @param rowBounds       分頁參數(shù)
               * @return
               */
              Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds);

              /**
               * 完成所有任務(wù)后
               */
              void afterAll();

              /**
               * 設(shè)置參數(shù)
               *
               * @param properties 插件屬性
               */
              void setProperties(Properties properties);
          }

          PageHelper中主要是對(duì)PageAutoDialect加了一層代理實(shí)現(xiàn),二者的代碼如下:

          public class PageHelper extends PageMethod implements Dialect {
              private PageParams pageParams;
              private PageAutoDialect autoDialect;

              @Override
              public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
                  if (ms.getId().endsWith(MSUtils.COUNT)) {
                      throw new RuntimeException("在系統(tǒng)中發(fā)現(xiàn)了多個(gè)分頁插件,請(qǐng)檢查系統(tǒng)配置!");
                  }
                  // 獲取上下文中的分頁信息
                  Page page = pageParams.getPage(parameterObject, rowBounds);
                  if (page == null) {
                      // 上下文中無分頁信息,跳過分頁攔截器
                      return true;
                  } else {
                      //設(shè)置默認(rèn)的 count 列
                      if (StringUtil.isEmpty(page.getCountColumn())) {
                          page.setCountColumn(pageParams.getCountColumn());
                      }
                      autoDialect.initDelegateDialect(ms);
                      return false;
                  }
              }

              /**
              * 代理模式
              */
              @Override
              public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
                  return autoDialect.getDelegate().beforeCount(ms, parameterObject, rowBounds);
              }

              /**
              * 代理模式
              */
              @Override
              public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) {
                  return autoDialect.getDelegate().afterCount(count, parameterObject, rowBounds);
              }

              @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) {
                  //這個(gè)方法即使不分頁也會(huì)被執(zhí)行,所以要判斷 null
                  AbstractHelperDialect delegate = autoDialect.getDelegate();
                  if (delegate != null) {
                      return delegate.afterPage(pageList, parameterObject, rowBounds);
                  }
                  return pageList;
              }

              @Override
              public void afterAll() {
                  AbstractHelperDialect delegate = autoDialect.getDelegate();
                  if (delegate != null) {
                      delegate.afterAll();
                      autoDialect.clearDelegate();
                  }
                  // 清理線程上下文中的分頁信息,防止影響后續(xù)的調(diào)用
                  clearPage();
              }
          }


          public abstract class AbstractHelperDialect extends AbstractDialect implements Constant {

              /**
               * 獲取分頁參數(shù)
               *
               * @param <T>
               * @return
               */
              public <T> Page<T> getLocalPage() {
                  return PageHelper.getLocalPage();
              }

              @Override
              public final boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
                  //該方法不會(huì)被調(diào)用
                  return true;
              }

              @Override
              public boolean beforeCount(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
                  // page配置中要求count
                  Page page = getLocalPage();
                  return !page.isOrderByOnly() && page.isCount();
              }


              @Override
              public boolean afterCount(long count, Object parameterObject, RowBounds rowBounds) {
                  Page page = getLocalPage();
                  page.setTotal(count);
                  if (rowBounds instanceof PageRowBounds) {
                      ((PageRowBounds) rowBounds).setTotal(count);
                  }
                  //pageSize < 0 的時(shí)候,不執(zhí)行分頁查詢
                  //pageSize = 0 的時(shí)候,還需要執(zhí)行后續(xù)查詢,但是不會(huì)分頁
                  if (page.getPageSize() < 0) {
                      return false;
                  }
                  return count > ((page.getPageNum() - 1) * page.getPageSize());
              }


              @Override
              public boolean beforePage(MappedStatement ms, Object parameterObject, RowBounds rowBounds) {
                  Page page = getLocalPage();
                  // 頁數(shù)大于0,需要進(jìn)行分頁查詢
                  if (page.isOrderByOnly() || page.getPageSize() > 0) {
                      return true;
                  }
                  return false;
              }

              // 該方法會(huì)在當(dāng)前SQL后面添加上分頁信息,需要查看MySqlDialect子類的實(shí)現(xiàn)
              @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);
              }


              @Override
              public Object afterPage(List pageList, Object parameterObject, RowBounds rowBounds) {
                  Page page = getLocalPage();
                  if (page == null) {
                      return pageList;
                  }
                  page.addAll(pageList);
                  if (!page.isCount()) {
                      page.setTotal(-1);
                  } else if ((page.getPageSizeZero() != null && page.getPageSizeZero()) && page.getPageSize() == 0) {
                      page.setTotal(pageList.size());
                  } else if(page.isOrderByOnly()){
                      page.setTotal(pageList.size());
                  }
                  return page;
              }

              @Override
              public void afterAll() {

              }

               @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();
              }
          }

          總結(jié)

          PageHelper本質(zhì)上是通過線程上下文變量來實(shí)現(xiàn),并且僅生效于設(shè)置分頁參數(shù)后的第一條SQL。因此需要先執(zhí)行分頁查詢,再執(zhí)行count。



          點(diǎn)擊左下角閱讀原文,到 SegmentFault 思否社區(qū) 和文章作者展開更多互動(dòng)和交流,掃描下方”二維碼“或在“公眾號(hào)后臺(tái)回復(fù)“ 入群 ”即可加入我們的技術(shù)交流群,收獲更多的技術(shù)文章~

          - END -


          瀏覽 33
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  在线中文字幕成人 | 日日干麻豆 | 国产a色 国产插骚 | 亚洲天堂无码aV | www蜜桃久久 |