1. 程式人生 > >Mybatis分頁外掛與limit分頁

Mybatis分頁外掛與limit分頁

1.mybatis自帶的分頁RowBounds;

Mybatis提供了一個簡單的邏輯分頁使用類RowBounds(物理分頁當然就是我們在sql語句中指定limit和offset值),在DefaultSqlSession提供的某些查詢介面中我們可以看到RowBounds是作為引數用來進行分頁的,如下介面:

 public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)

RowBounds原始碼如下:


public class RowBounds {
 
  /* 預設offset是0**/
  public static final int NO_ROW_OFFSET = 0;
  
  /* 預設Limit是int的最大值,因此它使用的是邏輯分頁**/
  public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
  public static final RowBounds DEFAULT = new RowBounds();
 
  private int offset;
  private int limit;
 
  public RowBounds() {
    this.offset = NO_ROW_OFFSET;
    this.limit = NO_ROW_LIMIT;
  }
 
  public RowBounds(int offset, int limit) {
    this.offset = offset;
    this.limit = limit;
  }
 
  public int getOffset() {
    return offset;
  }
 
  public int getLimit() {
    return limit;
  }
 
}

邏輯分頁的實現原理:

在DefaultResultSetHandler中,邏輯分頁會將所有的結果都查詢到,然後根據RowBounds中提供的offset和limit值來獲取最後的結果,DefaultResultSetHandler實現如下:

private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
      throws SQLException {
    DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
	//跳過RowBounds設定的offset值
    skipRows(rsw.getResultSet(), rowBounds);
	//判斷資料是否小於limit,如果小於limit的話就不斷的迴圈取值
    while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
      ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      Object rowValue = getRowValue(rsw, discriminatedResultMap);
      storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
    }
  }
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException {
	//判斷資料是否小於limit,小於返回true
    return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
  }
  //跳過不需要的行,應該就是rowbounds設定的limit和offset
  private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
    if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
      if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
        rs.absolute(rowBounds.getOffset());
      }
    } else {
	  //跳過RowBounds中設定的offset條資料
      for (int i = 0; i < rowBounds.getOffset(); i++) {
        rs.next();
      }
    }
  }

總結:Mybatis的邏輯分頁比較簡單,簡單來說就是取出所有滿足條件的資料,然後捨棄掉前面offset條資料,然後再取剩下的資料的limit條

2.mybatis外掛或者直接書寫sql進行分頁

          (1).通過自己的封裝SQL根據beginNum(開始條數)和endNum(需要的條數)來進行分頁

           (2).PageHelper分頁外掛

分頁外掛的原理就是實現mybatis的攔截器,實現裡面的方法:

--> mybatis自帶分頁RowBounds:   //邏輯分頁

      Java:   

        RowBounds rb=new RowBounds(offset, limit);  //offset(從多少條開始);limit(獲取多少條)

       SqlSession sqlSession=sqlSessionFactory.openSession();//sqlSessionFactory通過讀取mybatis配置檔案的輸入流然後通過new SqlSeesionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));最終得到SqlSessionFactory;

         List<Student> studentlist=sqlSession.selectList("xx.xx.Mapper.findStudent",null,rb);//第一個引數為具體Mapper檔案的下的findStudent的ID,第二個引數為提供的條件引數,第三個引數為我們要進行對獲取學生進行分頁

         sqlSession.close();

         return studentlist;

      Mapper:   

        <select id="findStudent" resultType="Student">

            select * from Student

         </select>

      備註:通過以上例子,很明顯的看出,在分頁的時候,我們是把所有的資料都查詢出來,然後通過RowBounds進行在記憶體分頁.通過原始碼檢視,也是通過ResuleSet結果集進行分頁;

    --> mybatis自寫sql或者通過分頁外掛PageHelper:   //物理分頁

        (1).mybatis PageHelper分頁外掛 

        Mapper:

            <select id="findStudent" resultType="Student">

                select * from Student

           </select>

        Dao層-StudentDao:

            List<Student> findStudent();

        Service層:

             PageHelper.startPage(pageNum,pageSize);//pageNum 頁數  pageSize 數量

           List<Student> stu=studentDao.findStudent();  //studentDao @Autowried註解獲取; 在執行查詢資料時,就會自動執行2個sql;執行上述Mapper下的ID為findStudent的sql 自動執行分頁,通過PageHelper進行識別是何資料庫拼接分頁語句,若是mysql,自動通過limit分頁,若是oracle自動通過rownum進行分頁,另一個會自動拼接Mapper下不存在的ID為findStudent_COUNT,查詢的總數;可以通過列印的日誌進行跟蹤;

           PageInfo<Student> page = new PageInfo<Student>(stu); //自動封裝總數count以及分頁,資料返回頁面

           return page;//返回分頁之後的資料

      (2).mybatis 自行使用SQL進行分頁

            例:     

           SQL程式碼(mysql資料庫):

               A:select * from Student  LIMIT #{beginNum,jdbcType=INTEGER},#{endNum,jdbcType=INTEGER}    //beginNum開始條數;endNum需要的條數

                B:select count(0) from Student

           JAVA:

                Map<String,Object> map=new HashMap<String,Object>();

                 map.put("beginNum",beginNum); 

                map.put("endNum",endNum);  //從第beginNum條開始,讀取後面endNum條

                List<Student> studentlist=studentDao.findStudent(Map<String,Object> map);  //studentDao學生dao層

                 Integer count=studentDao.count();//獲取總數返回頁面

                 //需要手動進行封裝總數以及分頁資訊,資料返回頁面;       

        備註:檢視如上例子程式碼,我們就發現了是直接通過SQL進行在資料庫中直接分頁,得到的資料就是我們想要分頁之後的資料,就是物理分頁;

下面有個例項是在我用外掛是遇到的:

使用分頁外掛在查詢的時候會分成兩個sql語句來執行的,一個是查詢出總共有多少條資料,在查出對應的頁碼資料。

如下面的sql語句在執行的時候會分出兩個sql:

 SELECT iiid,title,detail,makeDate,paperMediaSource,infoUrl,keyWord,(SELECT imgUrl 
 FROM INFO_IMG img WHERE img.iiid=item.iiid) AS imgUrl 
   from info_item item WHERE item.makeDate<=#{expireDate} AND ChanNum='010' 
  AND InfoCls='001179' ORDER BY item.makeDate DESC

分成:

1、SELECT count(*) FROM info_item item WHERE item.makeDate <= ? AND ChanNum = '010' AND InfoCls = '001179'

2、SELECT TOP 20 iiid, title, detail, makeDate, paperMediaSource, infoUrl, keyWord, imgUrl
 FROM (SELECT ROW_NUMBER() OVER ( ORDER BY item.makeDate DESC) PAGE_ROW_NUMBER, iiid, title, detail, 
 makeDate, paperMediaSource, infoUrl, keyWord, (SELECT imgUrl FROM INFO_IMG img WHERE img.iiid = item.iiid)
 AS imgUrl FROM info_item item WHERE item.makeDate <= '2018-08-30 23:59:59' AND ChanNum = '010' AND InfoCls = '001179')
 AS PAGE_TABLE_ALIAS WHERE PAGE_ROW_NUMBER > 0 ORDER BY PAGE_ROW_NUMBER 

    總結:

    1:邏輯分頁 記憶體開銷比較大,在資料量比較小的情況下效率比物理分頁高;在資料量很大的情況下,記憶體開銷過大,容易記憶體溢位,不建議使用

    2:物理分頁 記憶體開銷比較小,在資料量比較小的情況下效率比邏輯分頁還是低,在資料量很大的情況下,建議使用物理分頁