Mybatis分頁插件PageHelper的配置和使用方法


 

 

  • 前言

在web開發過程中涉及到表格時,例如dataTable,就會產生分頁的需求,通常我們將分頁方式分為兩種:前端分頁和后端分頁。

前端分頁

一次性請求數據表格中的所有記錄(ajax),然后在前端緩存並且計算count和分頁邏輯,一般前端組件(例如dataTable)會提供分頁動作。

特點是:簡單,很適合小規模的web平台;當數據量大的時候會產生性能問題,在查詢和網絡傳輸的時間會很長。

后端分頁

在ajax請求中指定頁碼(pageNum)和每頁的大小(pageSize),后端查詢出當頁的數據返回,前端只負責渲染。

特點是:復雜一些;性能瓶頸在MySQL的查詢性能,這個當然可以調優解決。一般來說,web開發使用的是這種方式。

我們說的也是后端分頁。

 

  • MySQL對分頁的支持

簡單來說MySQL對分頁的支持是通過limit子句。請看下面的例子。

 
limit關鍵字的用法是
LIMIT [offset,] rows
offset是相對於首行的偏移量(首行是0),rows是返回條數。

# 每頁10條記錄,取第一頁,返回的是前10條記錄
select * from tableA limit 0,10; # 每頁10條記錄,取第二頁,返回的是第11條記錄,到第20條記錄, select * from tableA limit 10,10;
 

這里提一嘴的是,MySQL在處理分頁的時候是這樣的:

limit 1000,10 - 過濾出1010條數據,然后丟棄前1000條,保留10條。當偏移量大的時候,性能會有所下降。

limit 100000,10 - 會過濾10w+10條數據,然后丟棄前10w條。如果在分頁中發現了性能問題,可以根據這個思路調優。

 

  • Mybatis分頁插件PageHelper

在使用Java Spring開發的時候,Mybatis算是對數據庫操作的利器了。不過在處理分頁的時候,Mybatis並沒有什么特別的方法,一般需要自己去寫limit子句實現,成本較高。好在有個PageHelper插件。

1、POM依賴

Mybatis的配置就不多提了。PageHelper的依賴如下。需要新的版本可以去maven上自行選擇

1
2
3
4
5
<dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version> 4.1 . 4 </version>
  </dependency>

 

2、Mybatis對PageHelper的配置

打開Mybatis配置文件,一般在Resource路徑下。我這里叫mybatis-config.xml。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<?xml version= "1.0"  encoding="UTF-8"?>
 
<!DOCTYPE configuration PUBLIC  "-//mybatis.org//DTD Config 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-config.dtd">
 
<configuration>
<!-- 全局參數 -->
<settings>
     <!-- 使全局的映射器啟用或禁用緩存。 -->
     <setting name= "cacheEnabled"  value="true"/>
     <!-- 全局啟用或禁用延遲加載。當禁用時,所有關聯對象都會即時加載。 -->
     <setting name= "lazyLoadingEnabled"  value="true"/>
     <!-- 當啟用時,有延遲加載屬性的對象在被調用時將會完全加載任意屬性。否則,每種屬性將會按需要加載。 -->
     <setting name= "aggressiveLazyLoading"  value="true"/>
     <!-- 是否允許單條sql 返回多個數據集  (取決於驅動的兼容性)  default : true  -->
     <setting name= "multipleResultSetsEnabled"  value="true"/>
     <!-- 是否可以使用列的別名 (取決於驅動的兼容性)  default : true  -->
     <setting name= "useColumnLabel"  value="true"/>
     <!-- 允許JDBC 生成主鍵。需要驅動器支持。如果設為了 true ,這個設置將強制使用被生成的主鍵,有一些驅動器不兼容不過仍然可以執行。   default : false   -->
     <setting name= "useGeneratedKeys"  value="true"/>
     <!-- 指定 MyBatis 如何自動映射 數據基表的列 NONE:不隱射 PARTIAL:部分  FULL:全部  -->
     <setting name= "autoMappingBehavior"  value="PARTIAL"/>
     <!-- 這是默認的執行類型  (SIMPLE: 簡單; REUSE: 執行器可能重復使用prepared statements語句;BATCH: 執行器可以重復執行語句和批量更新)  -->
     <setting name= "defaultExecutorType"  value="SIMPLE"/>
     <!-- 使用駝峰命名法轉換字段。 -->
     <setting name= "mapUnderscoreToCamelCase"  value="true"/>
     <!-- 設置本地緩存范圍 session:就會有數據的共享  statement:語句范圍 (這樣就不會有數據的共享 ) defalut:session -->
     <setting name= "localCacheScope"  value="SESSION"/>
     <!-- 設置但JDBC類型為空時,某些驅動程序 要指定值, default :OTHER,插入空值時不需要指定類型 -->
     <setting name= "jdbcTypeForNull"  value="NULL"/>
</settings>
 
<plugins>
     <plugin interceptor= "com.github.pagehelper.PageHelper" >
         <property name= "dialect"  value="mysql"/>
         <property name= "offsetAsPageNum"  value="false"/>
         <property name= "rowBoundsWithCount"  value="false"/>
         <property name= "pageSizeZero"  value="true"/>
         <property name= "reasonable"  value="false"/>
         <property name= "supportMethodsArguments"  value="false"/>
         <property name= "returnPageInfo"  value="none"/>
     </plugin>
</plugins>
</configuration>  

這里要注意的是PageHelper相關的配置。 

 

如果你沒有加載Mybatis配置文件,那么使用的是Mybatis默認的配置。如何加載Mybatis配置文件呢?

到你的dataSrouce配置中。

在配置sqlSessionFactory的時候,指定Mybatis核心配置文件和mapper的路徑,代碼如下

1
2
3
4
5
6
7
8
9
@Bean (name =  "moonlightSqlSessionFactory" )
@Primary
public  SqlSessionFactory moonlightSqlSessionFactory(@Qualifier("moonlightData") DataSource dataSource) throws Exception {
     SqlSessionFactoryBean bean =  new  SqlSessionFactoryBean();
     bean.setDataSource(dataSource);
     bean.setMapperLocations( new  PathMatchingResourcePatternResolver().getResources("classpath:mybatis-mapper/*.xml"));
     bean.setConfigLocation( new  ClassPathResource("mybatis-config.xml"));
     return  bean.getObject();
} 

說明:

這里配置的mapper.xml存放路徑,在Resource/mybatis-mapper文件夾下

這里配置的mybatis-config.xml文件,在Resource/下

 

 

3、分頁

准備一個mapper.xml,測試就隨便寫一個吧,干脆就用工程里的一個。

這里這個查詢,是一個典型的多條件查詢,我們要做的是對多條件匹配到的記錄進行分頁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?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">
<mapper namespace= "com.kangaroo.studio.moonlight.dao.mapper.MoonlightMapper" >
   <resultMap id= "geoFenceList"  type="com.kangaroo.studio.moonlight.dao.model.GeoFence">
     <constructor>
       <idArg column= "id"  javaType="java.lang.Integer" jdbcType="INTEGER" />
       <arg column= "name"  javaType="java.lang.String" jdbcType="VARCHAR" />
       <arg column= "type"  javaType="java.lang.Integer" jdbcType="INTEGER" />
       <arg column= "group"  javaType="java.lang.String" jdbcType="VARCHAR" />
       <arg column= "geo"  javaType="java.lang.String" jdbcType="VARCHAR" />
       <arg column= "createTime"  javaType="java.lang.String" jdbcType="VARCHAR" />
       <arg column= "updateTime"  javaType="java.lang.String" jdbcType="VARCHAR" />
     </constructor>
   </resultMap>
 
   <sql id= "base_column" >id, name, type, `group`, geo, createTime, updateTime </sql>
 
   <select id= "queryGeoFence"  parameterType="com.kangaroo.studio.moonlight.dao.model.GeoFenceQueryParam" resultMap="geoFenceList">
     select <include refid= "base_column" /> from geoFence where  1 = 1
     < if  test="type != null">
       and type = #{type}
     </ if >
     < if  test="name != null">
       and name like concat( '%' , #{name}, '%' )
     </ if >
     < if  test="group != null">
       and `group` like concat( '%' , #{group}, '%' )
     </ if >
     < if  test="startTime != null">
       and createTime >= #{startTime}
     </ if >
     < if  test="endTime != null">
       and createTime <= #{endTime}
     </ if >
   </select>
</mapper>

  

在Mapper.java接口中編寫對應的方法

1
List<GeoFence> queryGeoFence(GeoFenceQueryParam geoFenceQueryParam);

  

先上分頁代碼,后面再說明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@RequestMapping (value =  "/fence/query" , method = RequestMethod.POST)
     @ResponseBody
     public  ResponseEntity<Response> queryFence(@RequestBody GeoFenceQueryParam geoFenceQueryParam) {
         try  {
             Map<String, Object> data =  new  HashMap<>();
             Integer pageNum = geoFenceQueryParam.getPageNum()!= null ?geoFenceQueryParam.getPageNum(): 1 ;
             Integer pageSize = geoFenceQueryParam.getPageSize()!= null ?geoFenceQueryParam.getPageSize(): 10 ;
             Page page = PageHelper.startPage(pageNum, pageSize,  true );
             List<GeoFence> list = moonlightMapper.queryGeoFence(geoFenceQueryParam);
             data.put( "total" , page.getTotal());
             data.put( "nowPage" , pageNum);
             data.put( "data" , list);
             return  new ResponseEntity<>(
                     new  Response(ResultCode.SUCCESS, "查詢geoFence成功", data),
                     HttpStatus.OK);
         }  catch  (Exception e) {
             logger.error( "查詢geoFence失敗" , e);
             return  new ResponseEntity<>(
                     new  Response(ResultCode.EXCEPTION, "查詢geoFence失敗", null),
                     HttpStatus.INTERNAL_SERVER_ERROR);
         }
     }

 

 

說明:

1、PageHelper的優點是,分頁和Mapper.xml完全解耦。實現方式是以插件的形式,對Mybatis執行的流程進行了強化,添加了總數count和limit查詢。屬於物理分頁。

2、Page page = PageHelper.startPage(pageNum, pageSize, true); - true表示需要統計總數,這樣會多進行一次請求select count(0); 省略掉true參數只返回分頁數據。 

1)統計總數,(將SQL語句變為 select count(0) from xxx,只對簡單SQL語句其效果,復雜SQL語句需要自己寫)

    Page<?> page = PageHelper.startPage(1,-1);

    long count = page.getTotal();

2)分頁,pageNum - 第N頁, pageSize - 每頁M條數

    A、只分頁不統計(每次只執行分頁語句)

    PageHelper.startPage([pageNum],[pageSize]);

    List<?> pagelist = queryForList( xxx.class, "queryAll" , param);

    //pagelist就是分頁之后的結果

    B、分頁並統計(每次執行2條語句,一條select count語句,一條分頁語句)適用於查詢分頁時數據發生變動,需要將實時的變動信息反映到分頁結果上

    Page<?> page = PageHelper.startPage([pageNum],[pageSize],[iscount]);

    List<?> pagelist = queryForList( xxx.class , "queryAll" , param);

    long count = page.getTotal();

    //也可以 List<?> pagelist = page.getList();  獲取分頁后的結果集

3)使用PageHelper查全部(不分頁)

    PageHelper.startPage(1,0);

    List<?> alllist = queryForList( xxx.class , "queryAll" , param);

4)PageHelper的其他API

    String orderBy = PageHelper.getOrderBy();    //獲取orderBy語句

    Page<?> page = PageHelper.startPage(Object params);

    Page<?> page = PageHelper.startPage(int pageNum, int pageSize);

    Page<?> page = PageHelper.startPage(int pageNum, int pageSize, boolean isCount);

    Page<?> page = PageHelper.startPage(pageNum, pageSize, orderBy);

    Page<?> page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable);    //isReasonable分頁合理化,null時用默認配置

    Page<?> page = PageHelper.startPage(pageNum, pageSize, isCount, isReasonable, isPageSizeZero);    //isPageSizeZero是否支持PageSize為0,true且pageSize=0時返回全部結果,false時分頁,null時用默認配置

5)、默認值

    //RowBounds參數offset作為PageNum使用 - 默認不使用

    private boolean offsetAsPageNum = false;

    //RowBounds是否進行count查詢 - 默認不查詢

    private boolean rowBoundsWithCount = false;

    //當設置為true的時候,如果pagesize設置為0(或RowBounds的limit=0),就不執行分頁,返回全部結果

    private boolean pageSizeZero = false;

    //分頁合理化

    private boolean reasonable = false;

    //是否支持接口參數來傳遞分頁參數,默認false

    private boolean supportMethodsArguments = false;  

 

3、有一個安全性問題,需要注意一下,不然可能導致分頁錯亂。我這里直接粘貼了這篇博客里的一段話。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
4 . 什么時候會導致不安全的分頁?
 
PageHelper 方法使用了靜態的 ThreadLocal 參數,分頁參數和線程是綁定的。
 
只要你可以保證在 PageHelper 方法調用后緊跟 MyBatis 查詢方法,這就是安全的。因為 PageHelper 在  finally  代碼段中自動清除了 ThreadLocal 存儲的對象。
 
如果代碼在進入 Executor 前發生異常,就會導致線程不可用,這屬於人為的 Bug(例如接口方法和 XML 中的不匹配,導致找不到 MappedStatement 時), 這種情況由於線程不可用,也不會導致 ThreadLocal 參數被錯誤的使用。
 
但是如果你寫出下面這樣的代碼,就是不安全的用法:
 
PageHelper.startPage( 1 ,  10 );
List<Country> list;
if (param1 !=  null ){
     list = countryMapper.selectIf(param1);
}  else  {
     list =  new  ArrayList<Country>();
}
這種情況下由於 param1 存在  null  的情況,就會導致 PageHelper 生產了一個分頁參數,但是沒有被消費,這個參數就會一直保留在這個線程上。當這個線程再次被使用時,就可能導致不該分頁的方法去消費這個分頁參數,這就產生了莫名其妙的分頁。
 
上面這個代碼,應該寫成下面這個樣子:
 
List<Country> list;
if (param1 !=  null ){
     PageHelper.startPage( 1 ,  10 );
     list = countryMapper.selectIf(param1);
}  else  {
     list =  new  ArrayList<Country>();
}
這種寫法就能保證安全。
 
如果你對此不放心,你可以手動清理 ThreadLocal 存儲的分頁參數,可以像下面這樣使用:
 
List<Country> list;
if (param1 !=  null ){
     PageHelper.startPage( 1 ,  10 );
     try {
         list = countryMapper.selectAll();
     }  finally  {
         PageHelper.clearPage();
     }
}  else  {
     list =  new  ArrayList<Country>();
}
這么寫很不好看,而且沒有必要。

  

官方文檔,給你參考:

https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md

轉自:https://www.cnblogs.com/kangoroo/p/7998433.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM