查看pagehelper/Mybatis-PageHelper項目的github,先了解下最新版本

點擊maven center可以跳轉search.maven.org

找到右側maven,復制依賴
pom 引入Pagehelper分頁插件
<!--集成mybatis pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.2.0</version>
</dependency>
這里先跳過如何一步一步的實現(xiàn)流程,我們先來看看上面的依賴帶來的問題。
從下面的圖片可以看到,查詢接口的時候,返回的分頁數(shù)據(jù)明顯是不對??偣?條數(shù)據(jù),分頁第十頁,前面跳過90條數(shù)據(jù),那么必然是返回空數(shù)據(jù)的,結果明顯是有問題的。

github上面的依賴用在springboot上可能有點問題,我們需要使用專門的springboot的版本pagehelper-spring-boot-starter插件
在search.maven.org查詢pagehelper-spring-boot-starter

從上面查詢到最新的版本是1.3.0,pom.xml添加依賴如下:
<!--集成mybatis pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
無數(shù)據(jù)的時候返回空

注意上面的分析是基于application.yml中pageHelper的reasonable設置為false的前提下的
reasonable為true,如果存在數(shù)據(jù),但是該分頁沒有數(shù)據(jù)的時候會返回最后一條數(shù)據(jù)
配置分頁插件
application.yml添加如下幾行配置信息
#pageHelper分頁插件
pageHelper:
helperDialect: mysql
reasonable: false #如果開啟分頁優(yōu)化,在分頁的時候如果沒有數(shù)據(jù),會顯示有數(shù)據(jù)頁面的數(shù)據(jù)
supportMethodsArguments: true
params: count=countSql
使用pageHelper實現(xiàn)分頁功能
1.創(chuàng)建UserEntity實體類
@JsonInclude(JsonInclude.Include.NON_NULL)
@JsonIgnoreProperties(value = {"password"})
public class UserEntity {
/** 會員級別ID;1->黃金會員;2->白金會員;3->鉆石會員;4->普通會員 */
public int memberLevelId ;
/** 用戶名 */
public String username ;
/** 密碼 */
public String password ;
/** 手機號碼 */
public String phone ;
/** 昵稱 */
public String nickname ;
/** 帳號啟用狀態(tài);0->禁用;1->啟用 */
public int status ;
/** 注冊時間 */
public String createTime ;
}
2.數(shù)據(jù)操作層Mapper類查詢出所有數(shù)據(jù)
@Repository
public interface UserMapper {
List<UserEntity> getUserList();
}
UserMapper.xml
<?xml version="1.0" encoding="utf-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<!--<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">-->
<mapper namespace="com.springboot.demo.mapper.UserMapper">
<select id="getUserList" resultType="com.springboot.demo.entity.UserEntity">
SELECT
*
FROM
ums_member
</select>
</mapper>
3、創(chuàng)建業(yè)務接口
@Repository
public class UserService {
@Autowired
private UserMapper mUserMapper;
public List<UserEntity> getUserList() {
return mUserMapper.getUserList();
}
}
4、控制層實現(xiàn)分頁
工具類
public class DataUtil {
/**
* 獲取分頁數(shù)據(jù)
* @param list
* @param <T>
* @return
*/
public static <T>Map<String,Object> getPageData(List<T> list){
PageInfo<T> pageInfo = new PageInfo<>(list);
Map<String, Object> data = new HashMap<>();
data.put("total",pageInfo.getTotal());
data.put("list",pageInfo.getList());
return data;
}
}
PageInfo中的一些我們需要用到的參數(shù)如下表:
| PageInfo.list | 結果集 |
| PageInfo.pageNum | 當前頁碼 |
| PageInfo.pageSize | 當前頁面顯示的數(shù)據(jù)條目 |
| PageInfo.pages | 總頁數(shù) |
| PageInfo.total | 數(shù)據(jù)的總條目數(shù) |
| PageInfo.prePage | 上一頁 |
| PageInfo.nextPage | 下一頁 |
| PageInfo.isFirstPage | 是否為第一頁 |
| PageInfo.isLastPage | 是否為最后一頁 |
| PageInfo.hasPreviousPage | 是否有上一頁 |
| PageHelper.hasNextPage | 是否有下一頁 |
Controller增加分頁支持
@RestController
@RequestMapping(value = "/user")
@Api(tags = {"Member"})
public class UserController {
@Autowired
private UserService mUserService;
@ApiOperation(value = "獲取用戶列表")
@RequestMapping(value = "/users",method = RequestMethod.GET)
public ResponseEntity getUserList(@RequestParam(value = "pageIndex",defaultValue = "1") @ApiParam("起始頁碼") int pageIndex,
@RequestParam(value = "pageSize",required = true,defaultValue = "10") @ApiParam("每頁顯示數(shù)量") int pageSize){
PageHelper.startPage(pageIndex,pageSize);
List<UserEntity> list = mUserService.getUserList();
return ResponseEntity.success(DataUtil.getPageData(list));
}
}
=============================================
下面知識記錄
分頁插件參數(shù)介紹
分頁插件提供了多個可選參數(shù),這些參數(shù)使用時,按照上面兩種配置方式中的示例配置即可。
分頁插件可選參數(shù)如下:
-
dialect:默認情況下會使用 PageHelper 方式進行分頁,如果想要實現(xiàn)自己的分頁邏輯,可以實現(xiàn)Dialect(com.github.pagehelper.Dialect) 接口,然后配置該屬性為實現(xiàn)類的全限定名稱。
下面幾個參數(shù)都是針對默認 dialect 情況下的參數(shù)。使用自定義 dialect 實現(xiàn)時,下面的參數(shù)沒有任何作用。
helperDialect:分頁插件會自動檢測當前的數(shù)據(jù)庫鏈接,自動選擇合適的分頁方式。 你可以配置helperDialect屬性來指定分頁插件使用哪種方言。配置時,可以使用下面的縮寫值:
oracle,mysql,mariadb,sqlite,hsqldb,postgresql,db2,sqlserver,informix,h2,sqlserver2012,derby
特別注意:使用 SqlServer2012 數(shù)據(jù)庫時,需要手動指定為sqlserver2012,否則會使用 SqlServer2005 的方式進行分頁。
你也可以實現(xiàn)AbstractHelperDialect,然后配置該屬性為實現(xiàn)類的全限定名稱即可使用自定義的實現(xiàn)方法。offsetAsPageNum:默認值為false,該參數(shù)對使用RowBounds作為分頁參數(shù)時有效。 當該參數(shù)設置為true時,會將RowBounds中的offset參數(shù)當成pageNum使用,可以用頁碼和頁面大小兩個參數(shù)進行分頁。rowBoundsWithCount:默認值為false,該參數(shù)對使用RowBounds作為分頁參數(shù)時有效。 當該參數(shù)設置為true時,使用RowBounds分頁會進行 count 查詢。pageSizeZero:默認值為false,當該參數(shù)設置為true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結果(相當于沒有執(zhí)行分頁查詢,但是返回結果仍然是Page類型)。reasonable:分頁合理化參數(shù),默認值為false。當該參數(shù)設置為true時,pageNum<=0時會查詢第一頁,pageNum>pages(超過總數(shù)時),會查詢最后一頁。默認false時,直接根據(jù)參數(shù)進行查詢。params:為了支持startPage(Object params)方法,增加了該參數(shù)來配置參數(shù)映射,用于從對象中根據(jù)屬性名取值, 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認值, 默認值為pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。supportMethodsArguments:支持通過 Mapper 接口參數(shù)來傳遞分頁參數(shù),默認值false,分頁插件會從查詢方法的參數(shù)值中,自動根據(jù)上面params配置的字段中取值,查找到合適的值時就會自動分頁。 使用方法可以參考測試代碼中的com.github.pagehelper.test.basic包下的ArgumentsMapTest和ArgumentsObjTest。autoRuntimeDialect:默認值為false。設置為true時,允許在運行時根據(jù)多數(shù)據(jù)源自動識別對應方言的分頁 (不支持自動選擇sqlserver2012,只能使用sqlserver),用法和注意事項參考下面的場景五。closeConn:默認值為true。當使用運行時動態(tài)數(shù)據(jù)源或沒有設置helperDialect屬性自動獲取數(shù)據(jù)庫類型時,會自動獲取一個數(shù)據(jù)庫連接, 通過該屬性來設置是否關閉獲取的這個連接,默認true關閉,設置為false后,不會關閉獲取的連接,這個參數(shù)的設置要根據(jù)自己選擇的數(shù)據(jù)源來決定。
重要提示:
當 offsetAsPageNum=false 的時候,由于 PageNum 問題,RowBounds查詢的時候 reasonable 會強制為 false。使用 PageHelper.startPage 方法不受影響。
如何選擇配置這些參數(shù)
單獨看每個參數(shù)的說明可能是一件讓人不爽的事情,這里列舉一些可能會用到某些參數(shù)的情況。
場景一
如果你仍然在用類似ibatis式的命名空間調用方式,你也許會用到rowBoundsWithCount, 分頁插件對RowBounds支持和 MyBatis 默認的方式是一致,默認情況下不會進行 count 查詢,如果你想在分頁查詢時進行 count 查詢, 以及使用更強大的 PageInfo 類,你需要設置該參數(shù)為 true。
注: PageRowBounds 想要查詢總數(shù)也需要配置該屬性為 true。
場景二
如果你仍然在用類似ibatis式的命名空間調用方式,你覺得 RowBounds 中的兩個參數(shù) offset,limit 不如 pageNum,pageSize 容易理解, 你可以使用 offsetAsPageNum 參數(shù),將該參數(shù)設置為 true 后,offset會當成 pageNum 使用,limit 和 pageSize 含義相同。
場景三
如果覺得某個地方使用分頁后,你仍然想通過控制參數(shù)查詢?nèi)康慕Y果,你可以配置 pageSizeZero 為 true, 配置后,當 pageSize=0 或者 RowBounds.limit = 0 就會查詢出全部的結果。
場景四
如果你分頁插件使用于類似分頁查看列表式的數(shù)據(jù),如新聞列表,軟件列表, 你希望用戶輸入的頁數(shù)不在合法范圍(第一頁到最后一頁之外)時能夠正確的響應到正確的結果頁面, 那么你可以配置 reasonable 為 true,這時如果 pageNum<=0 會查詢第一頁,如果 pageNum>總頁數(shù) 會查詢最后一頁。
場景五
如果你在 Spring 中配置了動態(tài)數(shù)據(jù)源,并且連接不同類型的數(shù)據(jù)庫,這時你可以配置 autoRuntimeDialect 為 true,這樣在使用不同數(shù)據(jù)源時,會使用匹配的分頁進行查詢。 這種情況下,你還需要特別注意 closeConn 參數(shù),由于獲取數(shù)據(jù)源類型會獲取一個數(shù)據(jù)庫連接,所以需要通過這個參數(shù)來控制獲取連接后,是否關閉該連接。 默認為 true,有些數(shù)據(jù)庫連接關閉后就沒法進行后續(xù)的數(shù)據(jù)庫操作。而有些數(shù)據(jù)庫連接不關閉就會很快由于連接數(shù)用完而導致數(shù)據(jù)庫無響應。所以在使用該功能時,特別需要注意你使用的數(shù)據(jù)源是否需要關閉數(shù)據(jù)庫連接。
當不使用動態(tài)數(shù)據(jù)源而只是自動獲取 helperDialect 時,數(shù)據(jù)庫連接只會獲取一次,所以不需要擔心占用的這一個連接是否會導致數(shù)據(jù)庫出錯,但是最好也根據(jù)數(shù)據(jù)源的特性選擇是否關閉連接。
如何在代碼中使用
閱讀前請注意看重要提示
分頁插件支持以下幾種調用方式:
//第一種,RowBounds方式的調用
List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));
//第二種,Mapper接口方式的調用,推薦這種使用方式。
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectIf(1);
//第三種,Mapper接口方式的調用,推薦這種使用方式。
PageHelper.offsetPage(1, 10);
List<Country> list = countryMapper.selectIf(1);
//第四種,參數(shù)方法調用
//存在以下 Mapper 接口方法,你不需要在 xml 處理后兩個參數(shù)
public interface CountryMapper {
List<Country> selectByPageNumSize(
@Param("user") User user,
@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代碼中直接調用:
List<Country> list = countryMapper.selectByPageNumSize(user, 1, 10);
//第五種,參數(shù)對象
//如果 pageNum 和 pageSize 存在于 User 對象中,只要參數(shù)有值,也會被分頁
//有如下 User 對象
public class User {
//其他fields
//下面兩個參數(shù)名和 params 配置的名字一致
private Integer pageNum;
private Integer pageSize;
}
//存在以下 Mapper 接口方法,你不需要在 xml 處理后兩個參數(shù)
public interface CountryMapper {
List<Country> selectByPageNumSize(User user);
}
//當 user 中的 pageNum!= null && pageSize!= null 時,會自動分頁
List<Country> list = countryMapper.selectByPageNumSize(user);
//第六種,ISelect 接口方式
//jdk6,7用法,創(chuàng)建接口
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
@Override
public void doSelect() {
countryMapper.selectGroupBy();
}
});
//jdk8 lambda用法
Page<Country> page = PageHelper.startPage(1, 10).doSelectPage(()-> countryMapper.selectGroupBy());
//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
@Override
public void doSelect() {
countryMapper.selectGroupBy();
}
});
//對應的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> countryMapper.selectGroupBy());
//count查詢,返回一個查詢語句的count數(shù)
long total = PageHelper.count(new ISelect() {
@Override
public void doSelect() {
countryMapper.selectLike(country);
}
});
//lambda
total = PageHelper.count(()->countryMapper.selectLike(country));
下面對最常用的方式進行詳細介紹
1). RowBounds方式的調用
List<Country> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(1, 10));
使用這種調用方式時,你可以使用RowBounds參數(shù)進行分頁,這種方式侵入性最小,我們可以看到,通過RowBounds方式調用只是使用了這個參數(shù),并沒有增加其他任何內(nèi)容。
分頁插件檢測到使用了RowBounds參數(shù)時,就會對該查詢進行物理分頁。
關于這種方式的調用,有兩個特殊的參數(shù)是針對 RowBounds 的,你可以參看上面的 場景一 和 場景二
注:不只有命名空間方式可以用RowBounds,使用接口的時候也可以增加RowBounds參數(shù),例如:
//這種情況下也會進行物理分頁查詢
List<Country> selectAll(RowBounds rowBounds);
注意: 由于默認情況下的 RowBounds 無法獲取查詢總數(shù),分頁插件提供了一個繼承自 RowBounds 的 PageRowBounds,這個對象中增加了 total 屬性,執(zhí)行分頁查詢后,可以從該屬性得到查詢總數(shù)。
2). PageHelper.startPage 靜態(tài)方法調用
除了 PageHelper.startPage 方法外,還提供了類似用法的 PageHelper.offsetPage 方法。
在你需要進行分頁的 MyBatis 查詢方法前調用 PageHelper.startPage 靜態(tài)方法即可,緊跟在這個方法后的第一個MyBatis 查詢方法會被進行分頁。
例一:
//獲取第1頁,10條內(nèi)容,默認查詢總數(shù)count
PageHelper.startPage(1, 10);
//緊跟著的第一個select方法會被分頁
List<Country> list = countryMapper.selectIf(1);
assertEquals(2, list.get(0).getId());
assertEquals(10, list.size());
//分頁時,實際返回的結果list類型是Page<E>,如果想取出分頁信息,需要強制轉換為Page<E>
assertEquals(182, ((Page) list).getTotal());
例二:
//request: url?pageNum=1&pageSize=10
//支持 ServletRequest,Map,POJO 對象,需要配合 params 參數(shù)
PageHelper.startPage(request);
//緊跟著的第一個select方法會被分頁
List<Country> list = countryMapper.selectIf(1);
//后面的不會被分頁,除非再次調用PageHelper.startPage
List<Country> list2 = countryMapper.selectIf(null);
//list1
assertEquals(2, list.get(0).getId());
assertEquals(10, list.size());
//分頁時,實際返回的結果list類型是Page<E>,如果想取出分頁信息,需要強制轉換為Page<E>,
//或者使用PageInfo類(下面的例子有介紹)
assertEquals(182, ((Page) list).getTotal());
//list2
assertEquals(1, list2.get(0).getId());
assertEquals(182, list2.size());
例三,使用PageInfo的用法:
//獲取第1頁,10條內(nèi)容,默認查詢總數(shù)count
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectAll();
//用PageInfo對結果進行包裝
PageInfo page = new PageInfo(list);
//測試PageInfo全部屬性
//PageInfo包含了非常全面的分頁屬性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());
3). 使用參數(shù)方式
想要使用參數(shù)方式,需要配置 supportMethodsArguments 參數(shù)為 true,同時要配置 params 參數(shù)。 例如下面的配置:
<plugins>
<!-- com.github.pagehelper為PageHelper類所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置參數(shù),后面會有所有的參數(shù)介紹 -->
<property name="supportMethodsArguments" value="true"/>
<property name="params" value="pageNum=pageNumKey;pageSize=pageSizeKey;"/>
</plugin>
</plugins>
在 MyBatis 方法中:
List<Country> selectByPageNumSize(
@Param("user") User user,
@Param("pageNumKey") int pageNum,
@Param("pageSizeKey") int pageSize);
當調用這個方法時,由于同時發(fā)現(xiàn)了 pageNumKey 和 pageSizeKey 參數(shù),這個方法就會被分頁。params 提供的幾個參數(shù)都可以這樣使用。
除了上面這種方式外,如果 User 對象中包含這兩個參數(shù)值,也可以有下面的方法:
List<Country> selectByPageNumSize(User user);
當從 User 中同時發(fā)現(xiàn)了 pageNumKey 和 pageSizeKey 參數(shù),這個方法就會被分頁。
注意:pageNum 和 pageSize 兩個屬性同時存在才會觸發(fā)分頁操作,在這個前提下,其他的分頁參數(shù)才會生效。
3). PageHelper 安全調用
1. 使用 RowBounds 和 PageRowBounds 參數(shù)方式是極其安全的
2. 使用參數(shù)方式是極其安全的
3. 使用 ISelect 接口調用是極其安全的
ISelect 接口方式除了可以保證安全外,還特別實現(xiàn)了將查詢轉換為單純的 count 查詢方式,這個方法可以將任意的查詢方法,變成一個 select count(*) 的查詢方法。
4. 什么時候會導致不安全的分頁?
PageHelper 方法使用了靜態(tài)的 ThreadLocal 參數(shù),分頁參數(shù)和線程是綁定的。
只要你可以保證在 PageHelper 方法調用后緊跟 MyBatis 查詢方法,這就是安全的。因為 PageHelper 在 finally 代碼段中自動清除了 ThreadLocal 存儲的對象。
如果代碼在進入 Executor 前發(fā)生異常,就會導致線程不可用,這屬于人為的 Bug(例如接口方法和 XML 中的不匹配,導致找不到 MappedStatement 時), 這種情況由于線程不可用,也不會導致 ThreadLocal 參數(shù)被錯誤的使用。
但是如果你寫出下面這樣的代碼,就是不安全的用法:
PageHelper.startPage(1, 10);
List<Country> list;
if(param1 != null){
list = countryMapper.selectIf(param1);
} else {
list = new ArrayList<Country>();
}
這種情況下由于 param1 存在 null 的情況,就會導致 PageHelper 生產(chǎn)了一個分頁參數(shù),但是沒有被消費,這個參數(shù)就會一直保留在這個線程上。當這個線程再次被使用時,就可能導致不該分頁的方法去消費這個分頁參數(shù),這就產(chǎn)生了莫名其妙的分頁。
上面這個代碼,應該寫成下面這個樣子:
List<Country> list;
if(param1 != null){
PageHelper.startPage(1, 10);
list = countryMapper.selectIf(param1);
} else {
list = new ArrayList<Country>();
}
這種寫法就能保證安全。
如果你對此不放心,你可以手動清理 ThreadLocal 存儲的分頁參數(shù),可以像下面這樣使用:
List<Country> list;
if(param1 != null){
PageHelper.startPage(1, 10);
try{
list = countryMapper.selectAll();
} finally {
PageHelper.clearPage();
}
} else {
list = new ArrayList<Country>();
}
這么寫很不好看,而且沒有必要。