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}翻到上一頁,