目錄
Mybatis專欄目錄(點(diǎn)擊進(jìn)入…)
@TOC
Mybatis分頁(yè)插件(PageHelper)
1.引入分頁(yè)插件(依賴/Jar)
引入分頁(yè)插件有下面2種方式,推薦使用Maven方式
(1)引入Jar包
可以從下面的地址中下載最新版本的jar包
https://oss.sonatype.org/content/repositories/releases/com/github/pagehelper/pagehelper/
http://repo1.maven.org/maven2/com/github/pagehelper/pagehelper/
由于使用了sql解析工具,還需要下載jsqlparser.jar:
http://repo1.maven.org/maven2/com/github/jsqlparser/jsqlparser/0.9.5/
(2)使用Maven在pom.xml中添加如下依賴。此版本依賴mybatis 3.4.6
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.8</version>
</dependency>
<!-- 編譯依賴項(xiàng) -->
<dependency>
<groupId>com.github.jsqlparser</groupId>
<artifactId>jsqlparser</artifactId>
<version>1.2</version>
</dependency>
最新版本號(hào)可以從首頁(yè)查看
(3)Spring Boot Starter(啟動(dòng)器)
//推薦使用下面這種方式
<!--pagehelper-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.3</version>
</dependency>
2.配置攔截器插件
特別注意,新版攔截器是com.github.pagehelper.PageInterceptor
com.github.pagehelper.PageHelper現(xiàn)在是一個(gè)特殊的dialect實(shí)現(xiàn)類,是分頁(yè)插件的默認(rèn)實(shí)現(xiàn)類,提供了和以前相同的用法
(1)在MyBatis核心配置文件(Xml)中配置攔截器插件
<plugins>
<!-- com.github.pagehelper為PageHelper類所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql" />
</plugin>
</plugins>
plugins在配置文件中的位置必須符合要求(順序),否則會(huì)報(bào)錯(cuò)
順序如下:
①properties
②settings
③typeAliases
④typeHandlers
⑤objectFactory
⑥objectWrapperFactory
⑦plugins
⑧environments
⑨databaseIdProvider
⑩mapper
(2)在Spring配置文件中配置攔截器插件
使用Spring的屬性配置方式,可以使用plugins屬性像下面這樣配置:
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注意其他配置 -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!--使用下面的方式配置參數(shù),一行配置一個(gè) -->
<value>
<!--使用的數(shù)據(jù)庫(kù)類型 -->
helperDialect=mysql
reasonable=true
supportMethodsArguments=true
params=count=countSql
autoRuntimeDialect=true
</value>
</property>
</bean>
</array>
</property>
</bean>
(3)Spring Boot(application.properties)
#分頁(yè)插件
pagehelper.helper-dialect=mysql
pagehelper.params=count=countSql
pagehelper.reasonable=true
pagehelper.support-methods-arguments=true
分頁(yè)插件參數(shù)介紹
分頁(yè)插件提供了多個(gè)可選參數(shù),這些參數(shù)使用時(shí),按照上面兩種配置方式中的示例配置即可
dialect:默認(rèn)情況下會(huì)使用PageHelper方式進(jìn)行分頁(yè),如果想要實(shí)現(xiàn)自己的分頁(yè)邏輯,可以實(shí)現(xiàn) Dialect(com.github.pagehelper.Dialect)接口,然后配置該屬性為實(shí)現(xiàn)類的全限定名稱
下面幾個(gè)參數(shù)都是針對(duì)默認(rèn)dialect情況下的參數(shù)。使用自定義dialect實(shí)現(xiàn)時(shí),下面的參數(shù)沒(méi)有任何作用
(1)helperDialect:數(shù)據(jù)庫(kù)方言
分頁(yè)插件會(huì)自動(dòng)檢測(cè)當(dāng)前的數(shù)據(jù)庫(kù)鏈接,自動(dòng)選擇合適的分頁(yè)方式??梢耘渲胔elperDialect屬性來(lái)指定分頁(yè)插件使用哪種方言
配置時(shí),可以使用下面的縮寫(xiě)值:
oracle、mysql、mariadb、sqlite、hsqldb、postgresql、db2
sqlserver、informix、h2、sqlserver2012、derby
特別注意:使用Sql Server 2012數(shù)據(jù)庫(kù)時(shí),需要手動(dòng)指定為Sql Server 2012,否則會(huì)使用Sql Server 2005的方式進(jìn)行分頁(yè)。也可以實(shí)現(xiàn)AbstractHelperDialect,然后配置該屬性為實(shí)現(xiàn)類的全限定名稱即可使用自定義的實(shí)現(xiàn)方法。
(2)offsetAsPageNum:RowBounds的offset作pageNum
該參數(shù)對(duì)使用RowBounds作為分頁(yè)參數(shù)時(shí)有效。當(dāng)該參數(shù)設(shè)置為true時(shí),會(huì)將RowBounds中的offset參數(shù)當(dāng)成pageNum使用,可以用頁(yè)碼和頁(yè)面大小兩個(gè)參數(shù)進(jìn)行分頁(yè)(默認(rèn)值為false)
(3)rowBoundsWithCount:RowBounds進(jìn)行count查詢
該參數(shù)對(duì)使用RowBounds作為分頁(yè)參數(shù)時(shí)有效。當(dāng)該參數(shù)設(shè)置為true時(shí),使用RowBounds分頁(yè)會(huì)進(jìn)行count查詢(默認(rèn)值為false)
(4)pageSizeZero:查詢?nèi)拷Y(jié)果
當(dāng)該參數(shù)設(shè)置為true時(shí)。如果pageSize = 0或者RowBounds.limit = 0就會(huì)查詢出全部的結(jié)果(相當(dāng)于沒(méi)有執(zhí)行分頁(yè)查詢,但是返回結(jié)果仍然是Page類型)(默認(rèn)值為false)
(5)reasonable:查詢第一頁(yè)、最后一頁(yè)、參數(shù)查詢
分頁(yè)合理化參數(shù),默認(rèn)值為false。當(dāng)該參數(shù)設(shè)置為true時(shí),pageNum<=0時(shí)會(huì)查詢第一頁(yè), pageNum>pages(超過(guò)總數(shù)時(shí)),會(huì)查詢最后一頁(yè)。為false時(shí),直接根據(jù)參數(shù)進(jìn)行查詢
(6)params:參數(shù)映射
為了支持startPage(Object params)方法,增加了該參數(shù)來(lái)配置參數(shù)映射,用于從對(duì)象中根據(jù)屬性名取值,可以配置pageNum、pageSize、count、pageSizeZero、reasonable
不配置映射的用默認(rèn)值,默認(rèn)值為
pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero
(7)supportMethodsArguments
支持通過(guò)Mapper接口參數(shù)來(lái)傳遞分頁(yè)參數(shù),默認(rèn)值false,分頁(yè)插件會(huì)從查詢方法的參數(shù)值中,自動(dòng)根據(jù)上面params配置的字段中取值,查找到合適的值時(shí)就會(huì)自動(dòng)分頁(yè)。使用方法可以參考測(cè)試代碼中的com.github.pagehelper.test.basic包下的ArgumentsMapTest和ArgumentsObjTest
(8)autoRuntimeDialect
設(shè)置為true時(shí),允許在運(yùn)行時(shí)根據(jù)多數(shù)據(jù)源自動(dòng)識(shí)別對(duì)應(yīng)方言的分頁(yè)(不支持自動(dòng)選擇sqlserver2012,只能使用sqlserver),用法和注意事項(xiàng)參考下面的場(chǎng)景五(默認(rèn)值為 false)
(9)closeConn:是否關(guān)閉數(shù)據(jù)庫(kù)連接
當(dāng)使用運(yùn)行時(shí)動(dòng)態(tài)數(shù)據(jù)源或沒(méi)有設(shè)置helperDialect屬性自動(dòng)獲取數(shù)據(jù)庫(kù)類型時(shí),會(huì)自動(dòng)獲取一個(gè)數(shù)據(jù)庫(kù)連接,通過(guò)該屬性來(lái)設(shè)置是否關(guān)閉獲取的這個(gè)連接,默認(rèn)true關(guān)閉,設(shè)置為false后,不會(huì)關(guān)閉獲取的連接,這個(gè)參數(shù)的設(shè)置要根據(jù)自己選擇的數(shù)據(jù)源來(lái)決定(默認(rèn)值為true)
重要提示:
當(dāng)offsetAsPageNum=false的時(shí)候,由于PageNum問(wèn)題,RowBounds查詢的時(shí)候reasonable會(huì)強(qiáng)制為false。使用PageHelper.startPage()不受影響
如何選擇配置這些參數(shù)
場(chǎng)景一
如果仍然在用類似Mybatis式的命名空間調(diào)用方式,也許會(huì)用到rowBoundsWithCount,分頁(yè)插件對(duì)RowBounds支持和MyBatis默認(rèn)的方式是一致,默認(rèn)情況下不會(huì)進(jìn)行count查詢,如果想在分頁(yè)查詢時(shí)進(jìn)行count查詢,以及使用更強(qiáng)大的PageInfo類,需要設(shè)置該參數(shù)為true
注意:PageRowBounds想要查詢總數(shù)也需要配置該屬性為true
場(chǎng)景二
如果仍然在用類似Mybatis式的命名空間調(diào)用方式,RowBounds中的兩個(gè)參數(shù)offset、limit不如 pageNum、pageSize容易理解,可以使用offsetAsPageNum參數(shù),將該參數(shù)設(shè)置為true后,offset會(huì)當(dāng)成pageNum使用,limit和pageSize含義相同
場(chǎng)景三
如果覺(jué)得某個(gè)地方使用分頁(yè)后,仍然想通過(guò)控制參數(shù)查詢?nèi)康慕Y(jié)果,可以配置pageSizeZero為 true,配置后,當(dāng)pageSize=0或者RowBounds.limit = 0就會(huì)查詢出全部的結(jié)果
場(chǎng)景四
如果分頁(yè)插件使用于類似分頁(yè)查看列表式的數(shù)據(jù)。如新聞列表、軟件列表,希望用戶輸入的頁(yè)數(shù)不在合法范圍(第一頁(yè)到最后一頁(yè)之外)時(shí)能夠正確的響應(yīng)到正確的結(jié)果頁(yè)面,那么可以配置reasonable為true,這時(shí)如果pageNum<=0會(huì)查詢第一頁(yè),如果pageNum>總頁(yè)數(shù)會(huì)查詢最后一頁(yè)
場(chǎng)景五
如果在Spring中配置了動(dòng)態(tài)數(shù)據(jù)源。并且連接不同類型的數(shù)據(jù)庫(kù),這時(shí)可以配置 autoRuntimeDialect為true,這樣在使用不同數(shù)據(jù)源時(shí),會(huì)使用匹配的分頁(yè)進(jìn)行查詢。這種情況下,還需要特別注意closeConn參數(shù),由于獲取數(shù)據(jù)源類型會(huì)獲取一個(gè)數(shù)據(jù)庫(kù)連接,所以需要通過(guò)這個(gè)參數(shù)來(lái)控制獲取連接后,是否關(guān)閉該連接。 默認(rèn)為true,有些數(shù)據(jù)庫(kù)連接關(guān)閉后就沒(méi)法進(jìn)行后續(xù)的數(shù)據(jù)庫(kù)操作。而有些數(shù)據(jù)庫(kù)連接不關(guān)閉就會(huì)很快由于連接數(shù)用完而導(dǎo)致數(shù)據(jù)庫(kù)無(wú)響應(yīng)。所以在使用該功能時(shí),特別需要注意你使用的數(shù)據(jù)源是否需要關(guān)閉數(shù)據(jù)庫(kù)連接。
當(dāng)不使用動(dòng)態(tài)數(shù)據(jù)源而只是自動(dòng)獲取 helperDialect 時(shí),數(shù)據(jù)庫(kù)連接只會(huì)獲取一次,所以不需要擔(dān)心占用的這一個(gè)連接是否會(huì)導(dǎo)致數(shù)據(jù)庫(kù)出錯(cuò),但是最好也根據(jù)數(shù)據(jù)源的特性選擇是否關(guān)閉連接
分頁(yè)注意
(1)PageHelper.startPage()重要提示
只有緊跟在PageHelper.startPage方法后的第一個(gè)Mybatis的查詢(Select)方法會(huì)被分頁(yè)
(2)不要配置多個(gè)分頁(yè)插件
請(qǐng)不要在系統(tǒng)中配置多個(gè)分頁(yè)插件(使用Spring時(shí)。mybatis-config.xml和Spring配置方式,選擇其中一種,不要同時(shí)配置多個(gè)分頁(yè)插件)
(3)分頁(yè)插件不支持帶有for update語(yǔ)句的分頁(yè)
對(duì)于帶有for update的sql,會(huì)拋出運(yùn)行時(shí)異常,對(duì)于這樣的sql建議手動(dòng)分頁(yè),畢竟這樣的sql需要重視
(4)分頁(yè)插件不支持嵌套結(jié)果映射
由于嵌套結(jié)果方式會(huì)導(dǎo)致結(jié)果集被折疊,因此分頁(yè)查詢的結(jié)果在折疊后總數(shù)會(huì)減少,所以無(wú)法保證分頁(yè)結(jié)果數(shù)量正確
3.常用分頁(yè)方式詳細(xì)
(1)RowBounds方式的調(diào)用(mybatis)
Mybatis提供了一個(gè)簡(jiǎn)單的邏輯分頁(yè)使用類RowBounds,在DefaultSqlSession提供的某些查詢接口中我們可以看到RowBounds是作為參數(shù)用來(lái)進(jìn)行分頁(yè),邏輯分頁(yè)會(huì)將所有的結(jié)果都查詢到,然后根據(jù)RowBounds中提供的offset和limit值來(lái)獲取最后的結(jié)果。
RowBounds();
RowBounds(int offset, int limit);
參數(shù):
offset:偏移量
limit:每頁(yè)展示多少數(shù)據(jù)
List<Student> list = studentMapper.find(new RowBounds(0, 10));
Page page = ((Page) list;
Page<Student> page = studentMapper.find(new RowBounds(0, 10));
mappep.xml里面正常配置,不用對(duì)rowBounds任何操作。mybatis的攔截器自動(dòng)操作rowBounds進(jìn)行分頁(yè)。使用RowBounds最大好處就是節(jié)省了在xml再拼裝limit
總結(jié):Mybatis的邏輯分頁(yè)比較簡(jiǎn)單,簡(jiǎn)單來(lái)說(shuō)就是取出所有滿足條件的數(shù)據(jù),然后舍棄掉前面offset條數(shù)據(jù),然后再取剩下的數(shù)據(jù)的limit條
使用這種調(diào)用方式時(shí),可以使用RowBounds參數(shù)進(jìn)行分頁(yè),這種方式侵入性最小,可以看到,通過(guò)RowBounds方式調(diào)用只是使用了這個(gè)參數(shù),并沒(méi)有增加其他任何內(nèi)容
分頁(yè)插件檢測(cè)到使用了RowBounds參數(shù)時(shí),就會(huì)對(duì)該查詢進(jìn)行物理分頁(yè)。關(guān)于這種方式的調(diào)用,有兩個(gè)特殊的參數(shù)是針對(duì)RowBounds(場(chǎng)景一和場(chǎng)景二)
注意:不只有命名空間方式可以用RowBounds,使用接口的時(shí)候也可以增加RowBounds參數(shù)。
// 這種情況下也會(huì)進(jìn)行物理分頁(yè)查詢
List<Country> selectAll(RowBounds rowBounds);
注意:由于默認(rèn)情況下的RowBounds無(wú)法獲取查詢總數(shù),分頁(yè)插件提供了一個(gè)繼承自RowBounds的PageRowBounds,這個(gè)對(duì)象中增加了total屬性,執(zhí)行分頁(yè)查詢后,可以從該屬性得到查詢總數(shù)。
(2)PageHelper.offsetPage()靜態(tài)方法
//start:開(kāi)始處,rowNum:數(shù)目
PageHelper.offsetPage(int offset, int limit);
List<User> list = userMapper.selectAll();
跟startPage()使用一樣,只是限制、參數(shù)不同。
(3)PageHelper.startPage靜態(tài)方法調(diào)用(推薦)
除了PageHelper.startPage方法外,還提供了類似用法的PageHelper.offsetPage方法
在需要進(jìn)行分頁(yè)的MyBatis查詢方法前調(diào)用PageHelper.startPage靜態(tài)方法即可,緊跟在這個(gè)方法后的第一個(gè)MyBatis查詢方法會(huì)被進(jìn)行分頁(yè)
//獲取第1頁(yè),10條內(nèi)容,默認(rèn)查詢總數(shù)count
PageHelper.startPage(1, 10);
//緊跟著的第一個(gè)select方法會(huì)被分頁(yè),后面的不會(huì)被分頁(yè),除非再次調(diào)用PageHelper.startPage()
List<User> list = userMapper.selectAll();
//分頁(yè)時(shí),實(shí)際返回的結(jié)果list類型是Page<E>,如果想取出分頁(yè)信息,需要強(qiáng)制轉(zhuǎn)換為Page<E>或者使用PageInfo類包裝
// ①Page
Page page = (Page) list;
page.getTotal();
// ②PageInfo
PageInfo page = new PageInfo(list);
page.getTotal();
(4)使用參數(shù)方法方式
想要使用參數(shù)方式,需要配置supportMethodsArguments參數(shù)為 true,同時(shí)要配置params參數(shù)。
<plugins>
<!-- com.github.pagehelper為PageHelper類所在包名 -->
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- 配置supportMethodsArguments=true -->
<property name="supportMethodsArguments" value="true" />
<property name="params"
value="pageNum=pageNumKey;pageSize=pageSizeKey;" />
</plugin>
</plugins>
在MyBatis Mapper方法中
List<Country> selectByPageNumSize(
@Param("user") User user,
@Param("pageNumKey") int pageNum,
@Param("pageSizeKey") int pageSize);
// 在代碼中直接調(diào)用:
List<User> userList = userMapper.selectByPageNumSize(user, 1, 10);
當(dāng)調(diào)用這個(gè)方法時(shí),由于同時(shí)發(fā)現(xiàn)了pageNumKey和pageSizeKey參數(shù),這個(gè)方法就會(huì)被分頁(yè)。params 提供的幾個(gè)參數(shù)都可以這樣使用
(5)參數(shù)對(duì)象方式
除了上面這種方式外,如果 User 對(duì)象中包含這兩個(gè)參數(shù)值,也可以有下面的方法:
// 如果pageNum和pageSize存在于User對(duì)象中,只要參數(shù)有值,也會(huì)被分頁(yè)
public class User {
// 其他fields
// 下面兩個(gè)參數(shù)名和params配置的名字一致
private Integer pageNum;
private Integer pageSize;
}
// 存在以下Mapper接口方法,不需要在xml處理后兩個(gè)參數(shù)
public interface UserMapper {
List<User> selectByPageNumSize(User user);
}
// 當(dāng)user中的pageNum!=null&&pageSize!=null時(shí),會(huì)自動(dòng)分頁(yè)
List<User> listUser = userMapper.selectByPageNumSize(user);
當(dāng)從User中同時(shí)發(fā)現(xiàn)了pageNum Key和pageSize Key參數(shù),這個(gè)方法就會(huì)被分頁(yè)
注意:pageNum和pageSize兩個(gè)屬性同時(shí)存在才會(huì)觸發(fā)分頁(yè)操作,在這個(gè)前提下,其他的分頁(yè)參數(shù)才會(huì)生效。
(6)ISelect接口方式
//第六種,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();
}
});
//對(duì)應(yīng)的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> countryMapper.selectGroupBy());
//count查詢,返回一個(gè)查詢語(yǔ)句的count數(shù)
long total = PageHelper.count(new ISelect() {
@Override
public void doSelect() {
countryMapper.selectLike(country);
}
});
//lambda
total = PageHelper.count(()->countryMapper.selectLike(country));
PageHelper安全調(diào)用
1.使用RowBounds和PageRowBounds參數(shù)方式是極其安全的
2.使用參數(shù)方式是極其安全的
3.使用ISelect接口調(diào)用是極其安全的
ISelect接口方式除了可以保證安全外,還特別實(shí)現(xiàn)了將查詢轉(zhuǎn)換為單純的count查詢方式,這個(gè)方法可以將任意的查詢方法,變成一個(gè)select count(*)的查詢方法。
PageHelper.startPage()什么時(shí)候會(huì)導(dǎo)致不安全的分頁(yè)?解決?
PageHelper.startPage()方法使用了靜態(tài)的ThreadLocal參數(shù),分頁(yè)參數(shù)和線程是綁定的。只要可以保證在PageHelper方法調(diào)用后緊跟MyBatis查詢方法,就是安全的。因?yàn)镻ageHelper在finally代碼段中自動(dòng)清除了ThreadLocal存儲(chǔ)的對(duì)象
如果代碼在進(jìn)入Executor前發(fā)生異常,就會(huì)導(dǎo)致線程不可用,這屬于人為的Bug(例如接口方法和XML中的不匹配,導(dǎo)致找不到MappedStatement 時(shí)),這種情況由于線程不可用,也不會(huì)導(dǎo)致ThreadLocal參數(shù)被錯(cuò)誤的使用。
(1)不安全的用法
PageHelper.startPage(1, 10);
List<User> list;
if (param != null) {
list = userMapper.selectAll(param);
} else {
list = new ArrayList<User>();
}
這種情況下由于param存在null的情況,就會(huì)導(dǎo)致PageHelper生產(chǎn)了一個(gè)分頁(yè)參數(shù),但是沒(méi)有被消費(fèi),這個(gè)參數(shù)就會(huì)一直保留在這個(gè)線程上。當(dāng)這個(gè)線程再次被使用時(shí),就可能導(dǎo)致不該分頁(yè)的方法去消費(fèi)這個(gè)分頁(yè)參數(shù),這就產(chǎn)生了莫名其妙的分頁(yè)。
上面這個(gè)代碼,應(yīng)該寫(xiě)成下面這個(gè)樣子:
List<User> list;
if (param != null) {
PageHelper.startPage(1, 10);
list = userMapper.selectAll(param);
} else {
list = new ArrayList<User>();
}
這種寫(xiě)法就能保證安全
如果對(duì)此不放心,可以手動(dòng)清理ThreadLocal存儲(chǔ)的分頁(yè)參數(shù)文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-775889.html
List<User> list;
if (param != null) {
PageHelper.startPage(1, 10);
try {
list = userMapper.selectAll(param);
} finally {
PageHelper.clearPage();
}
} else {
list = new ArrayList<User>();
}
這么寫(xiě)很不好看,而且沒(méi)有必要。文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-775889.html
到了這里,關(guān)于6.Mybatis分頁(yè)插件(PageHelper),解決PageHelper.startPage()不安全分頁(yè)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!