springboot 集成Mybatis 的pageHelper實現(xiàn)分頁功能

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

image.png

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

image.png

找到右側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ù)的,結果明顯是有問題的。


image.png

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

search.maven.org查詢pagehelper-spring-boot-starter

image.png

從上面查詢到最新的版本是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ù)的時候返回空


image.png

注意上面的分析是基于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ù)沒有任何作用。

  1. 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)方法。

  2. offsetAsPageNum:默認值為 false,該參數(shù)對使用 RowBounds 作為分頁參數(shù)時有效。 當該參數(shù)設置為 true 時,會將 RowBounds 中的 offset 參數(shù)當成 pageNum 使用,可以用頁碼和頁面大小兩個參數(shù)進行分頁。

  3. rowBoundsWithCount:默認值為false,該參數(shù)對使用 RowBounds 作為分頁參數(shù)時有效。 當該參數(shù)設置為true時,使用 RowBounds 分頁會進行 count 查詢。

  4. pageSizeZero:默認值為 false,當該參數(shù)設置為 true 時,如果 pageSize=0 或者 RowBounds.limit = 0 就會查詢出全部的結果(相當于沒有執(zhí)行分頁查詢,但是返回結果仍然是 Page 類型)。

  5. reasonable:分頁合理化參數(shù),默認值為false。當該參數(shù)設置為 true 時,pageNum<=0 時會查詢第一頁, pageNum>pages(超過總數(shù)時),會查詢最后一頁。默認false 時,直接根據(jù)參數(shù)進行查詢。

  6. params:為了支持startPage(Object params)方法,增加了該參數(shù)來配置參數(shù)映射,用于從對象中根據(jù)屬性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認值, 默認值為pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。

  7. supportMethodsArguments:支持通過 Mapper 接口參數(shù)來傳遞分頁參數(shù),默認值false,分頁插件會從查詢方法的參數(shù)值中,自動根據(jù)上面 params 配置的字段中取值,查找到合適的值時就會自動分頁。 使用方法可以參考測試代碼中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTestArgumentsObjTest。

  8. autoRuntimeDialect:默認值為 false。設置為 true 時,允許在運行時根據(jù)多數(shù)據(jù)源自動識別對應方言的分頁 (不支持自動選擇sqlserver2012,只能使用sqlserver),用法和注意事項參考下面的場景五。

  9. 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 使用,limitpageSize 含義相同。

場景三

如果覺得某個地方使用分頁后,你仍然想通過控制參數(shù)查詢?nèi)康慕Y果,你可以配置 pageSizeZerotrue, 配置后,當 pageSize=0 或者 RowBounds.limit = 0 就會查詢出全部的結果。

場景四

如果你分頁插件使用于類似分頁查看列表式的數(shù)據(jù),如新聞列表,軟件列表, 你希望用戶輸入的頁數(shù)不在合法范圍(第一頁到最后一頁之外)時能夠正確的響應到正確的結果頁面, 那么你可以配置 reasonabletrue,這時如果 pageNum<=0 會查詢第一頁,如果 pageNum>總頁數(shù) 會查詢最后一頁。

場景五

如果你在 Spring 中配置了動態(tài)數(shù)據(jù)源,并且連接不同類型的數(shù)據(jù)庫,這時你可以配置 autoRuntimeDialecttrue,這樣在使用不同數(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ù),分頁插件提供了一個繼承自 RowBoundsPageRowBounds,這個對象中增加了 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)了 pageNumKeypageSizeKey 參數(shù),這個方法就會被分頁。params 提供的幾個參數(shù)都可以這樣使用。

除了上面這種方式外,如果 User 對象中包含這兩個參數(shù)值,也可以有下面的方法:

List<Country> selectByPageNumSize(User user);

當從 User 中同時發(fā)現(xiàn)了 pageNumKeypageSizeKey 參數(shù),這個方法就會被分頁。

注意:pageNumpageSize 兩個屬性同時存在才會觸發(fā)分頁操作,在這個前提下,其他的分頁參數(shù)才會生效。

3). PageHelper 安全調用

1. 使用 RowBoundsPageRowBounds 參數(shù)方式是極其安全的
2. 使用參數(shù)方式是極其安全的
3. 使用 ISelect 接口調用是極其安全的

ISelect 接口方式除了可以保證安全外,還特別實現(xiàn)了將查詢轉換為單純的 count 查詢方式,這個方法可以將任意的查詢方法,變成一個 select count(*) 的查詢方法。

4. 什么時候會導致不安全的分頁?

PageHelper 方法使用了靜態(tài)的 ThreadLocal 參數(shù),分頁參數(shù)和線程是綁定的。

只要你可以保證在 PageHelper 方法調用后緊跟 MyBatis 查詢方法,這就是安全的。因為 PageHelperfinally 代碼段中自動清除了 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>();
}

這么寫很不好看,而且沒有必要。

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

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

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