在實際的項目開發(fā)中,常常需要使用到分頁,分頁方式分為兩種:前端分頁和后端分頁。
前端分頁
一次ajax請求數(shù)據(jù)的所有記錄,然后在前端緩存并且計算count和分頁邏輯,一般前端組件(例如dataTable)會提供分頁動作。
特點是:簡單,很適合小規(guī)模的web平臺;當(dāng)數(shù)據(jù)量大的時候會產(chǎn)生性能問題,在查詢和網(wǎng)絡(luò)傳輸?shù)臅r間會很長。
后端分頁
在ajax請求中指定頁碼pageNum和每頁的大小pageSize,后端查詢出當(dāng)頁的數(shù)據(jù)返回,前端只負(fù)責(zé)渲染。
特點是:復(fù)雜一些;性能瓶頸在MySQL的查詢性能,這個當(dāng)然可以調(diào)優(yōu)解決。一般來說,開發(fā)使用的是這種方式。
不使用分頁插件的分頁操作
在沒有使用分頁插件的時候需要先寫一個查詢count的select語句,然后再寫一個真正分頁查詢的語句,MySQL中有對分頁的支持,是通過limit子句
limit關(guān)鍵字的用法是:LIMIT [offset,] rows
offset是相對于首行的偏移量(首行是0),rows是返回條數(shù)。
例如:
每頁5條記錄,取第一頁,返回的是前5條記錄
select * from tableA limit 0,5;
每頁5條記錄,取第二頁,返回的是第6條記錄,到第10條記錄,
select * from tableA limit 5,5;
不過當(dāng)偏移量逐漸增大的時候,查詢速度可能就會變慢,性能會有所下降。
使用Mybatis分頁插件PageHelper
PageHelper是一款好用的開源免費的Mybatis第三方物理分頁插件
Github地址:https://github.com/pagehelper/Mybatis-PageHelper
官方地址:https://pagehelper.github.io/
在SpringBoot中使用PageHelper
首先要在pom.xml中配置PageHelper的依賴
在http://www.mvnrepository.com/中可以發(fā)現(xiàn)pagehelper有4.x和5.x兩個版本,用法有所不同,并不是向下兼容,在使用5.x版本的時候可能會報錯
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>4.2.1</version>
</dependency>
在Mybatis的配置文件中配置PageHelper插件
假如不配置在后面使用PageInfo類時就會出現(xiàn)問題,輸出結(jié)果的PageInfo屬性值基本上都是錯的
配置如下
<plugins>
<!-- com.github.pagehelper為PageHelper類所在包名 -->
<plugin interceptor="com.github.pagehelper.PageHelper">
<property name="dialect" value="mysql"/>
<!-- 該參數(shù)默認(rèn)為false -->
<!-- 設(shè)置為true時,會將RowBounds第一個參數(shù)offset當(dāng)成pageNum頁碼使用 -->
<!-- 和startPage中的pageNum效果一樣-->
<property name="offsetAsPageNum" value="false"/>
<!-- 該參數(shù)默認(rèn)為false -->
<!-- 設(shè)置為true時,使用RowBounds分頁會進(jìn)行count查詢 -->
<property name="rowBoundsWithCount" value="false"/>
<!-- 設(shè)置為true時,如果pageSize=0或者RowBounds.limit = 0就會查詢出全部的結(jié)果 -->
<!-- (相當(dāng)于沒有執(zhí)行分頁查詢,但是返回結(jié)果仍然是Page類型)-->
<property name="pageSizeZero" value="true"/>
<!-- 3.3.0版本可用 - 分頁參數(shù)合理化,默認(rèn)false禁用 -->
<!-- 啟用合理化時,如果pageNum<1會查詢第一頁,如果pageNum>pages會查詢最后一頁 -->
<!-- 禁用合理化時,如果pageNum<1或pageNum>pages會返回空數(shù)據(jù) -->
<property name="reasonable" value="true"/>
<!-- 3.5.0版本可用 - 為了支持startPage(Object params)方法 -->
<!-- 增加了一個`params`參數(shù)來配置參數(shù)映射,用于從Map或ServletRequest中取值 -->
<!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認(rèn)值 -->
<!--<property name="params" value="pageNum=start;pageSize=limit;pageSizeZero=zero;reasonable=heli;count=contsql"/>-->
</plugin>
</plugins>
上面是PageHelper官方給的配置和注釋,雖然寫的很多,不過確實描述的很明白。
dialect:標(biāo)識是哪一種數(shù)據(jù)庫,設(shè)計上必須。
offsetAsPageNum:將RowBounds第一個參數(shù)offset當(dāng)成pageNum頁碼使用
rowBoundsWithCount:設(shè)置為true時,使用RowBounds分頁會進(jìn)行count查詢
reasonable:value=true時,pageNum小于1會查詢第一頁,如果pageNum大于pageSize會查詢最后一頁
注:上面的配置只針對于pagehelper4.x版本的,如果你用的是pagehelper5.x版本就要這樣配置
官方推薦
1. 在 MyBatis 配置 xml 中配置攔截器插件
<!--
plugins在配置文件中的位置必須符合要求,否則會報錯,順序如下:
properties?, settings?,
typeAliases?, typeHandlers?,
objectFactory?,objectWrapperFactory?,
plugins?,
environments?, databaseIdProvider?, mappers?
-->
<plugins>
<!-- com.github.pagehelper為PageHelper類所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 使用下面的方式配置參數(shù),后面會有所有的參數(shù)介紹 -->
<property name="param1" value="value1"/>
</plugin>
</plugins>
2. 在 Spring 配置文件中配置攔截器插件
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注意其他配置 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置參數(shù),一行配置一個 -->
<value>
params=value1
</value>
</property>
</bean>
</array>
</property>
</bean>
個人推薦使用第一種,配置如下
<!-- 配置分頁插件 -->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 設(shè)置數(shù)據(jù)庫類型 Oracle,Mysql,MariaDB,SQLite,Hsqldb,PostgreSQL六種數(shù)據(jù)庫-->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
如果4.x的版本用了5.x的版本報錯信息如下
springboot在啟動項目的時候就會報錯,報錯信息有很多,主要是因為
Caused by: org.apache.ibatis.builder.BuilderException:
Error resolving class. Cause: org.apache.ibatis.type.TypeException:
Could not resolve type alias 'com.github.pagehelper.PageInterceptor'.
Caused by: org.apache.ibatis.type.TypeException:
Could not resolve type alias 'com.github.pagehelper.PageInterceptor'.
Caused by: java.lang.ClassNotFoundException:
Cannot find class: com.github.pagehelper.PageInterceptor
總的來說就是缺少了com.github.pagehelper.PageInterceptor,這個是新版攔截器,5.x版本才開始使用,所以在4.x版本這樣配置是不行的
那么5.x版本的配置在pagehelper4.x上能生效嗎?答案是不行
報錯信息如下
Caused by: org.apache.ibatis.builder.BuilderException:
Error parsing SQL Mapper Configuration. Cause:
java.lang.ClassCastException: com.github.pagehelper.PageHelper
cannot be cast to org.apache.ibatis.plugin.Interceptor
Caused by: java.lang.ClassCastException:
com.github.pagehelper.PageHelper
cannot be cast to org.apache.ibatis.plugin.Interceptor
新版的攔截器PageInterceptor不能和舊版攔截器相互轉(zhuǎn)換,所以還是不行的。
總的來說,pagehelper4.x就該用4.x的配置,pagehelper5.x就用5.x的配置(官方推薦)
項目中使用方法和結(jié)果
在配置完mybatis后,我簡單的說下pagehelper的業(yè)務(wù)用法,就以分頁查詢用戶列表為例
添加查詢所以用戶的mapper接口,對應(yīng)的sql語句我就不寫了
List<UserVo> listUser();
重點來了,然后在service中,先開啟分頁,然后把查詢結(jié)果集放入PageInfo中
public PageInfo listUserByPage(int pageNum, int pageSize) {
PageHelper.startPage(pageNum, pageSize);
List<UserVo> userVoList=userMapper.listUser();
PageInfo pageInfo=new PageInfo(userVoList);
return pageInfo;
}
PageHelper.startPage(pageNum, pageSize);這句非常重要,這段代碼表示分頁的開始,意思是從第pageNum頁開始,每頁顯示pageSize條記錄。
PageInfo這個類是插件里的類,這個類里面的屬性會在輸出結(jié)果中顯示,
使用PageInfo這個類,你需要將查詢出來的list放進(jìn)去:
PageHelper輸出的數(shù)據(jù)結(jié)構(gòu)
然后在controller層調(diào)用該方法設(shè)置對應(yīng)的pageNum和pageSize就可以了,我設(shè)置pageNum為1, pageSize為5,看個輸出結(jié)果吧
{
"msg": "獲取第1頁用戶信息成功",
"code": 200,
"data": {
"pageNum": 1,
"pageSize": 5,
"size": 5,
"orderBy": null,
"startRow": 1,
"endRow": 5,
"total": 11,
"pages": 3,
"list": [
{
"userId": "a24d0c3b-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081040"
},
{
"userId": "b0bc9e45-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081041"
},
{
"userId": "b44fd6ac-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081042"
},
{
"userId": "b7ac58f7-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081043"
},
{
"userId": "bbdeb5d8-2786-11e8-9835-e4f89cdc0d1f",
"username": "2015081044"
}
],
"prePage": 0,
"nextPage": 2,
"isFirstPage": true,
"isLastPage": false,
"hasPreviousPage": false,
"hasNextPage": true,
"navigatePages": 8,
"navigatepageNums": [
1,
2,
3
],
"navigateFirstPage": 1,
"navigateLastPage": 3,
"firstPage": 1,
"lastPage": 3
},
"success": true,
"error": null
}
PageInfo這個類里面的屬性:
pageNum當(dāng)前頁
pageSize每頁的數(shù)量
size當(dāng)前頁的數(shù)量
orderBy排序
startRow當(dāng)前頁面第一個元素在數(shù)據(jù)庫中的行號
endRow當(dāng)前頁面最后一個元素在數(shù)據(jù)庫中的行號
total總記錄數(shù)(在這里也就是查詢到的用戶總數(shù))
pages總頁數(shù) (這個頁數(shù)也很好算,每頁5條,總共有11條,需要3頁才可以顯示完)
list結(jié)果集
prePage前一頁
nextPage下一頁
isFirstPage是否為第一頁
isLastPage是否為最后一頁
hasPreviousPage是否有前一頁
hasNextPage是否有下一頁
navigatePages導(dǎo)航頁碼數(shù)
navigatepageNums所有導(dǎo)航頁號
navigateFirstPage導(dǎo)航第一頁
navigateLastPage導(dǎo)航最后一頁
firstPage第一頁
lastPage最后一頁
安全性
PageHelper 安全調(diào)用
1. 使用 RowBounds 和 PageRowBounds參數(shù)方式是極其安全的
2. 使用參數(shù)方式是極其安全的
3. 使用 ISelect 接口調(diào)用是極其安全的
ISelect接口方式除了可以保證安全外,還特別實現(xiàn)了將查詢轉(zhuǎn)換為單純的 count 查詢方式,這個方法可以將任意的查詢方法,變成一個 select count(*) 的查詢方法。
4. 什么時候會導(dǎo)致不安全的分頁?
PageHelper 方法使用了靜態(tài)的 ThreadLocal 參數(shù),分頁參數(shù)和線程是綁定的。
只要你可以保證在 PageHelper 方法調(diào)用后緊跟 MyBatis 查詢方法,這就是安全的。因為 PageHelper 在 finally 代碼段中自動清除了 ThreadLocal存儲的對象。
如果代碼在進(jìn)入 Executor 前發(fā)生異常,就會導(dǎo)致線程不可用,這屬于人為的 Bug(例如接口方法和 XML 中的不匹配,導(dǎo)致找不到 MappedStatement 時), 這種情況由于線程不可用,也不會導(dǎo)致 ThreadLocal 參數(shù)被錯誤的使用。
但是如果你寫出下面這樣的代碼,就是不安全的用法:
PageHelper.startPage(1, 10);
List<Country> list;
if(param1 != null){
list = countryMapper.selectIf(param1);
} else {
list = new ArrayList<Country>();
}
這種情況下由于 param1 存在null的情況,就會導(dǎo)致 PageHelper 生產(chǎn)了一個分頁參數(shù),但是沒有被消費,這個參數(shù)就會一直保留在這個線程上。當(dāng)這個線程再次被使用時,就可能導(dǎo)致不該分頁的方法去消費這個分頁參數(shù),這就產(chǎn)生了莫名其妙的分頁。
上面這個代碼,應(yīng)該寫成下面這個樣子:
List<Country> list;
if(param1 != null){
PageHelper.startPage(1, 10);
list = countryMapper.selectIf(param1);
} else {
list = new ArrayList<Country>();
}
這種寫法就能保證安全。