1. 程式人生 > >Mybatis框架外掛PageHelper的使用

Mybatis框架外掛PageHelper的使用

在web開發過程中涉及到表格時,例如dataTable,就會產生分頁的需求,通常我們將分頁方式分為兩種:前端分頁和後端分頁。

前端分頁

一次性請求資料表格中的所有記錄(ajax),然後在前端快取並且計算count和分頁邏輯,一般前端元件(例如dataTable)會提供分頁動作。

特點是:簡單,很適合小規模的web平臺;當資料量大的時候會產生效能問題,在查詢和網路傳輸的時間會很長。

後端分頁

在ajax請求中指定頁碼(pageNum)和每頁的大小(pageSize),後端查詢出當頁的資料返回,前端只負責渲染。

特點是:複雜一些;效能瓶頸在MySQL的查詢效能,這個當然可以調優解決。一般來說,web開發使用的是這種方式。

我們說的也是後端分頁。

 

  • MySQL對分頁的支援

簡單來說MySQL對分頁的支援是通過limit子句。請看下面的例子。

1 limit關鍵字的用法是
2 LIMIT [offset,] rows
3 offset是相對於首行的偏移量(首行是0),rows是返回條數。
4 
5 # 每頁10條記錄,取第一頁,返回的是前10條記錄
6 select * from tableA limit 0,10;
7 # 每頁10條記錄,取第二頁,返回的是第11條記錄,到第20條記錄,
8 select * from tableA limit 10,10;

這裡提一嘴的是,MySQL在處理分頁的時候是這樣的:

limit 1000,10 - 過濾出1010條資料,然後丟棄前1000條,保留10條。當偏移量大的時候,效能會有所下降。

limit 100000,10 - 會過濾10w+10條資料,然後丟棄前10w條。如果在分頁中發現了效能問題,可以根據這個思路調優。

 

  • Mybatis分頁外掛PageHelper

在使用Java Spring開發的時候,Mybatis算是對資料庫操作的利器了。不過在處理分頁的時候,Mybatis並沒有什麼特別的方法,一般需要自己去寫limit子句實現,成本較高。好在有個PageHelper外掛:

1、新增maven依賴

1 <dependency>
2     <groupId>com.github.pagehelper</groupId>
3
<artifactId>pagehelper</artifactId> 4 <version>5.0.0</version> 5 </dependency>

 

2、Mybatis對PageHelper進行配置(在Mybatis的配置檔案中,如 mybatis-config.xml 或 sqlMapConfig.xml)

 1     <!-- 配置分頁外掛 -->
 2     <plugins>
 3           <!-- com.github.pagehelper為PageHelper類所在包名 -->
 4           <plugin interceptor="com.github.pagehelper.PageHelper">
 5               <!-- 設定資料庫型別 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六種資料庫-->        
 6             <property name="dialect" value="mysql"/>
 7             <!-- 設定為true時,使用RowBounds分頁會進行count查詢 會去查詢出總數 -->
 8             <property name="rowBoundsWithCount" value="true"/>
 9           </plugin>
10     </plugins>

 

3、使用外掛進行分頁,mapper層不用進行修改,在service層進行操作

 1     /**
 2      * 獲取使用者集合
 3      * @param user   封裝好條件的user類
 4      * @param pageNo    第幾頁
 5      * @param pageSize   每頁顯示的長度
 6      * @return
 7      */
 8     @Override
 9     public PageInfo<SysUser> getSysUserList(SysUser user, int pageNo, int pageSize) {
10         PageHelper.startPage(pageNo,pageSize);
11         List<SysUser> userList = sysUserMapper.getSysUserList(user);
12         PageInfo<SysUser> pageinfo = new PageInfo<SysUser>(userList);
13         return pageinfo;
14     }

分頁外掛就是這樣使用,PageInfo 是引自com.github.pagehelper.PageInfo; 外掛對mybatis執行流程進行了增強,添加了limit以及count查詢,屬於物理分頁。

說明:

1、PageHelper的優點是,分頁和Mapper.xml完全解耦。實現方式是以外掛的形式,對Mybatis執行的流程進行了強化,添加了總數count和limit查詢。屬於物理分頁。

2、Page page = PageHelper.startPage(pageNum, pageSize, true); - true表示需要統計總數,這樣會多進行一次請求select count(0); 省略掉true引數只返回分頁資料。 

1)統計總數,(將SQL語句變為 select count(0) from xxx,只對簡單SQL語句其效果,複雜SQL語句需要自己寫)

    Page<?> page = PageHelper.startPage(1,-1);

    long count = page.getTotal();

2)分頁,pageNum - 第N頁, pageSize - 每頁M條數

    A、只分頁不統計(每次只執行分頁語句)

    PageHelper.startPage([pageNum],[pageSize]);

    List<?> pagelist = queryForList( xxx.class, "queryAll" , param);

    //pagelist就是分頁之後的結果

    B、分頁並統計(每次執行2條語句,一條select count語句,一條分頁語句)適用於查詢分頁時資料發生變動,需要將實時的變動資訊反映到分頁結果上

    Page<?> page = PageHelper.startPage([pageNum],[pageSize],[iscount]);

    List<?> pagelist = queryForList( xxx.class , "queryAll" , param);

    long count = page.getTotal();

    //也可以 List<?> pagelist = page.getList();  獲取分頁後的結果集

3)使用PageHelper查全部(不分頁)

    PageHelper.startPage(1,0);

    List<?> alllist = queryForList( xxx.class , "queryAll" , param);

4)PageHelper的其他API

    String orderBy = PageHelper.getOrderBy();    //獲取orderBy語句

    Page<?> page = PageHelper.startPage(Object params);

    Page<?> page = PageHelper.startPage(int pageNum, int pageSize);

    Page<?> page = PageHelper.startPage(int pageNum, int pageSize, boolean isCount);

    Page<?> page = PageHelper.startPage(pageNum, pageSize, orderBy);

    Page<?> page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable);    //isReasonable分頁合理化,null時用預設配置

    Page<?> page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable, isPageSizeZero);    //isPageSizeZero是否支援PageSize為0,true且pageSize=0時返回全部結果,false時分頁,null時用預設配置

5)、預設值

    //RowBounds引數offset作為PageNum使用 - 預設不使用

    private boolean offsetAsPageNum = false;

    //RowBounds是否進行count查詢 - 預設不查詢

    private boolean rowBoundsWithCount = false;

    //當設定為true的時候,如果pagesize設定為0(或RowBounds的limit=0),就不執行分頁,返回全部結果

    private boolean pageSizeZero = false;

    //分頁合理化

    private boolean reasonable = false;

    //是否支援介面引數來傳遞分頁引數,預設false

    private boolean supportMethodsArguments = false;  

 

3、有一個安全性問題,需要注意一下,不然可能導致分頁錯亂。我這裡直接貼上了這篇部落格裡的一段話。

 1 PageHelper 方法使用了靜態的 ThreadLocal 引數,分頁引數和執行緒是繫結的。
 2 
 3 只要你可以保證在 PageHelper 方法呼叫後緊跟 MyBatis 查詢方法,這就是安全的。因為 PageHelper 在 finally 程式碼段中自動清除了 ThreadLocal 儲存的物件。
 4 
 5 如果程式碼在進入 Executor 前發生異常,就會導致執行緒不可用,這屬於人為的 Bug(例如介面方法和 XML 中的不匹配,導致找不到 MappedStatement 時), 這種情況由於執行緒不可用,也不會導致 ThreadLocal 引數被錯誤的使用。
 6 
 7 但是如果你寫出下面這樣的程式碼,就是不安全的用法:
 8 
 9 PageHelper.startPage(1, 10);
10 List<Country> list;
11 if(param1 != null){
12     list = countryMapper.selectIf(param1);
13 } else {
14     list = new ArrayList<Country>();
15 }
16 這種情況下由於 param1 存在 null 的情況,就會導致 PageHelper 生產了一個分頁引數,但是沒有被消費,這個引數就會一直保留在這個執行緒上。當這個執行緒再次被使用時,就可能導致不該分頁的方法去消費這個分頁引數,這就產生了莫名其妙的分頁。
17 
18 上面這個程式碼,應該寫成下面這個樣子:
19 
20 List<Country> list;
21 if(param1 != null){
22     PageHelper.startPage(1, 10);
23     list = countryMapper.selectIf(param1);
24 } else {
25     list = new ArrayList<Country>();
26 }
27 這種寫法就能保證安全。
28 
29 如果你對此不放心,你可以手動清理 ThreadLocal 儲存的分頁引數,可以像下面這樣使用:
30 
31 List<Country> list;
32 if(param1 != null){
33     PageHelper.startPage(1, 10);
34     try{
35         list = countryMapper.selectAll();
36     } finally {
37         PageHelper.clearPage();
38     }
39 } else {
40     list = new ArrayList<Country>();
41 }
42 這麼寫很不好看,而且沒有必要。

 

4、最後附上PageInfo類的詳細屬性供參考:

 1  //當前頁
 2     private int pageNum;
 3     //每頁的數量
 4     private int pageSize;
 5     //當前頁的數量
 6     private int size;
 7     //排序
 8     private String orderBy;
 9 
10     //由於startRow和endRow不常用,這裡說個具體的用法
11     //可以在頁面中"顯示startRow到endRow 共size條資料"
12 
13     //當前頁面第一個元素在資料庫中的行號
14     private int startRow;
15     //當前頁面最後一個元素在資料庫中的行號
16     private int endRow;
17     //總記錄數
18     private long total;
19     //總頁數
20     private int pages;
21     //結果集
22     private List<T> list;
23 
24     //第一頁
25     private int firstPage;
26     //前一頁
27     private int prePage;
28     //下一頁
29     private int nextPage;
30     //最後一頁
31     private int lastPage;
32 
33     //是否為第一頁
34     private boolean isFirstPage = false;
35     //是否為最後一頁
36     private boolean isLastPage = false;
37     //是否有前一頁
38     private boolean hasPreviousPage = false;
39     //是否有下一頁
40     private boolean hasNextPage = false;
41     //導航頁碼數
42     private int navigatePages;
43     //所有導航頁號
44     private int[] navigatepageNums;

使用PageInfo這個類,你需要將查詢出來的list放進去:

    PageInfo<CityList> p=new PageInfo<CityList>(list);
然後將資料返回   mv.addObject("page", p);
這樣在頁面中就可以通過${page.nextPage}翻到下一頁,
${page.prePage}翻到上一頁,