在web開發中,數據的分頁是必不可少的。Pagehelper分頁插件很強大,雖說平時我們不需要用到它的很多功能,但是了解下還是有必要的。
官網:https://pagehelper.github.io/
注:在 MyBatis下使用。本節是在 SpringBoot整合mybatis 基礎上進行演示。
一、引入依賴
<!-- 核心啟動器, 包括auto-configuration、logging and YAML --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> </dependency> <!-- 數據庫操作需要的mysql 驅動包 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.48</version> </dependency> <!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.1.1</version> </dependency> <!-- pagehelper --> <dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper-spring-boot-starter</artifactId> <version>1.2.13</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> </dependency>
二、application.properties
#這里要注意&,可能在spring的xml中我們用的是轉義符號(&),但是在這里不用 spring.datasource.url=jdbc:mysql://192.168.178.5:12345/mydb?characterEncoding=utf8&useUnicode=true&useSSL=false&serverTimezone=UTC spring.datasource.username=root spring.datasource.password=123456 spring.datasource.driver-class-name=com.mysql.jdbc.Driver spring.datasource.type=com.zaxxer.hikari.HikariDataSource ####### mybatis ####### # 指定映射文件的具體位置 mybatis.mapper-locations=classpath:mapper/*.xml ####### pagehelper ####### # 默認情況下會使用 PageHelper 方式進行分頁,如果想要實現自己的分頁邏輯, # 可以實現 Dialect(com.github.pagehelper.Dialect) 接口,然后配置該屬性為實現類的全限定名稱。 # 下面幾個參數都是針對默認 dialect 情況下的參數。使用自定義 dialect 實現時(不推薦),下面的參數沒有任何作用# 分頁的數據庫語言, oracle/mysql pagehelper.helper-dialect=mysql # 該參數對使用 RowBounds 作為分頁參數時有效。 當該參數設置為 true 時, # 會將 RowBounds 中的 offset 參數當成 pageNum 使用,可以用頁碼和頁面大小兩個參數進行分頁。 pagehelper.offset-as-page-num= false # 該參數對使用 RowBounds 作為分頁參數時有效。 # 當該參數設置為true時,使用 RowBounds 分頁會進行 count 查詢。 pagehelper.row-bounds-with-count=false # 當該參數設置為 true 時,如果 pageSize=0 或者 RowBounds.limit = 0 # 就會查詢出全部的結果(相當於沒有執行分頁查詢,但是返回結果仍然是 Page 類型)。 pagehelper.page-size-zero=false # 分頁合理化參數,默認值為false。當該參數設置為 true 時,pageNum<=0 時會查詢第一頁, # pageNum>pages(超過總數時),會查詢最后一頁。默認false 時,直接根據參數進行查詢。 pagehelper.reasonable=true # 為了支持startPage(Object params)方法,增加了該參數來配置參數映射, # 用於從對象中根據屬性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable, # 不配置映射的用默認值, # 默認值為pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。 pagehelper.params=pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero # 支持通過 Mapper 接口參數來傳遞分頁參數,默認值false,分頁插件會從查詢方法的參數值中, # 自動根據上面 params 配置的字段中取值,查找到合適的值時就會自動分頁。 pagehelper.support-methods-arguments= # 當使用運行時動態數據源或沒有設置 helperDialect 屬性自動獲取數據庫類型時, # 會自動獲取一個數據庫連接, 通過該屬性來設置是否關閉獲取的這個連接,默認true關閉, # 設置為 false 后,不會關閉獲取的連接,這個參數的設置要根據自己選擇的數據源來決定。 pagehelper.close-conn=true # 默認值為 false。設置為 true 時,允許在運行時根據多數據源自動識別對應方言的分頁 # (不支持自動選擇sqlserver2012,只能使用sqlserver) pagehelper.auto-runtime-dialect=true
三、PageHelper.startPage 靜態方法調用
除了 PageHelper.startPage 方法外,還提供了類似用法的 PageHelper.offsetPage 方法。
在你需要進行分頁的 MyBatis 查詢方法前調用 PageHelper.startPage 靜態方法即可,緊跟在這個方法后的第一個MyBatis 查詢方法會被進行分頁。
public Page<Map<String,Object>> queryPage1(){ //獲取第1頁,3條內容,默認查詢總數count PageHelper.startPage(1, 3); //緊跟着的第一個select方法會被分頁 List<Map<String,Object>> list = userMapper.listUsers(); // 分頁時,實際返回的結果list類型是Page<E>,如果想取出分頁信息,需要強制轉換為Page<E> // 也可以這樣 PageInfo<Map<String, Object>> pageInfo = new PageInfo<Map<String, Object>>(list); Page<Map<String, Object>> pageInfo = (Page<Map<String, Object>>)list; return pageInfo; } public Page<Map<String,Object>> queryPage2(Map<String,Object> params){ //參數包含pageNum=1 和 pageSize=10 都可以直接這樣使用 //支持 ServletRequest,Map,POJO 對象 PageHelper.startPage(params); //緊跟着的第一個select方法會被分頁 List<Map<String,Object>> list = userMapper.listUsers(); Page<Map<String, Object>> pageInfo = (Page<Map<String, Object>>)list; //后面的不會被分頁,除非再次調用PageHelper.startPage List<Map<String,Object>> list2 = userMapper.listUsers(); System.out.println("list2的大小是:" + list2.size()); return pageInfo; } /** * 使用參數是安全的 * 想要使用參數方式,需要配置 supportMethodsArguments 參數為 true,同時要配置 params 參數 * 默認是pageSize和pageNum * 注:pageNum 和 pageSize 兩個屬性同時存在才會觸發分頁操作,在這個前提下,其他的分頁參數才會生效。 */ public Page<Map<String,Object>> queryPage3(Map<String,Object> params){ List<Map<String,Object>> list = userMapper.listUsers(params); Page<Map<String, Object>> pageInfo = (Page<Map<String, Object>>)list; return pageInfo; } /** * 使用 ISelect 接口調用是安全的 * ISelect 接口方式除了可以保證安全外,還特別實現了將查詢轉換為單純的 count 查詢方式, * 這個方法可以將任意的查詢方法,變成一個 select count(*) 的查詢方法。 * */ public PageInfo<Map<String,Object>> queryPage4(){ //分頁,返回PageInfo分頁對象 PageInfo<Map<String,Object>> pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() { @Override public void doSelect() { userMapper.listUsers(); } }); //count查詢,返回一個查詢語句的count數 long total = PageHelper.count(new ISelect() { @Override public void doSelect() { userMapper.listUsers(); } }); System.out.println("總記錄數:" + total); return pageInfo; }
四、什么時候會導致不安全的分頁
上面在講解的例子提到分頁安全,什么時候會導致不安全的分頁?
PageHelper 方法使用了靜態的 ThreadLocal 參數,分頁參數和線程是綁定的。
只要你可以保證在 PageHelper 方法調用后緊跟 MyBatis 查詢方法,這就是安全的。因為 PageHelper 在 finally 代碼段中自動清除了 ThreadLocal 存儲的對象。
如果代碼在進入 Executor 前發生異常,就會導致線程不可用,這屬於人為的 Bug(例如接口方法和 XML 中的不匹配,導致找不到 MappedStatement 時), 這種情況由於線程不可用,也不會導致 ThreadLocal 參數被錯誤的使用。
但是如果你寫出下面這樣的代碼,就是不安全的用法:
PageHelper.startPage(1, 10); List<Country> list; if(param1 != null){ list = countryMapper.selectIf(param1); } else { list = new ArrayList<Country>(); }
這種情況下由於 param1 存在 null 的情況,就會導致 PageHelper 生產了一個分頁參數,但是沒有被消費,這個參數就會一直保留在這個線程上。當這個線程再次被使用時,就可能導致不該分頁的方法去消費這個分頁參數,這就產生了莫名其妙的分頁。
上面的例子應該寫成:
List<Country> list; if(param1 != null){ PageHelper.startPage(1, 10); list = countryMapper.selectIf(param1); } else { list = new ArrayList<Country>(); }
