Mybatis分頁插件PageHelper

在實際的項目開發(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ā)使用的是這種方式。

不使用分頁插件的分頁操作

在沒有使用分頁插件的時候需要先寫一個查詢countselect語句,然后再寫一個真正分頁查詢的語句,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)pagehelper4.x5.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查詢
reasonablevalue=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)的pageNumpageSize就可以了,我設(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. 使用 RowBoundsPageRowBounds參數(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>();
}

這種寫法就能保證安全。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容