前言
在web開發過程中涉及到表格時,例如dataTable,就會產生分頁的需求,通常我們將分頁方式分為兩種:前端分頁和后端分頁。
前端分頁
一次性請求數據表格中的所有記錄(ajax),然后在前端緩存并且計算count和分頁邏輯,一般前端組件(例如dataTable)會提供分頁動作。
特點是:簡單,很適合小規模的web平臺;當數據量大的時候會產生性能問題,在查詢和網絡傳輸的時間會很長。
后端分頁
在ajax請求中指定頁碼(pageNum)和每頁的大小(pageSize),后端查詢出當頁的數據返回,前端只負責渲染。
特點是:復雜一些;性能瓶頸在MySQL的查詢性能,這個當然可以調優解決。一般來說,web開發使用的是這種方式。
我們說的也是后端分頁。
MySQL對分頁的支持
簡單來說MySQL對分頁的支持是通過limit子句。請看下面的例子。
limit關鍵字的用法是
1
|
LIMIT [offset,] rows |
offset是相對于首行的偏移量(首行是0),rows是返回條數。
1
2
3
4
|
# 每頁 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條。如果在分頁中發現了性能問題,可以根據這個思路調優。
在使用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
|
<?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
|
<?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
|
@RequestMapping (value = "/fence/query" , method = RequestMethod.POST) @ResponseBody public Response queryFence( @RequestBody GeoFenceQueryParam geoFenceQueryParam) { try { Integer pageNum = geoFenceQueryParam.getPageNum()!= null ?geoFenceQueryParam.getPageNum(): 1 ; Integer pageSize = geoFenceQueryParam.getPageSize()!= null ?geoFenceQueryParam.getPageSize(): 10 ; PageHelper.startPage(pageNum, pageSize); List<GeoFence> list = moonlightMapper.queryGeoFence(geoFenceQueryParam); return new Response(ResultCode.SUCCESS, "查詢geoFence成功" , list); } catch (Exception e) { logger.error( "查詢geoFence失敗" , e); return new Response(ResultCode.EXCEPTION, "查詢geoFence失敗" , null ); } } |
說明:
1、PageHelper的優點是,分頁和Mapper.xml完全解耦。實現方式是以插件的形式,對Mybatis執行的流程進行了強化,添加了總數count和limit查詢。屬于物理分頁。
2、有一個安全性問題,需要注意一下,不然可能導致分頁錯亂。我這里直接粘貼了這篇博客里的一段話。
4. 什么時候會導致不安全的分頁?
PageHelper 方法使用了靜態的 ThreadLocal 參數,分頁參數和線程是綁定的。
只要你可以保證在 PageHelper 方法調用后緊跟 MyBatis 查詢方法,這就是安全的。因為 PageHelper 在 finally 代碼段中自動清除了 ThreadLocal 存儲的對象。
如果代碼在進入 Executor 前發生異常,就會導致線程不可用,這屬于人為的 Bug(例如接口方法和 XML 中的不匹配,導致找不到 MappedStatement 時), 這種情況由于線程不可用,也不會導致 ThreadLocal 參數被錯誤的使用。
但是如果你寫出下面這樣的代碼,就是不安全的用法:
1
2
3
4
5
6
7
|
PageHelper.startPage( 1 , 10 ); List<Country> list; if (param1 != null ){ list = countryMapper.selectIf(param1); } else { list = new ArrayList<Country>(); } |
這種情況下由于 param1 存在 null 的情況,就會導致 PageHelper 生產了一個分頁參數,但是沒有被消費,這個參數就會一直保留在這個線程上。當這個線程再次被使用時,就可能導致不該分頁的方法去消費這個分頁參數,這就產生了莫名其妙的分頁。
上面這個代碼,應該寫成下面這個樣子:
1
2
3
4
5
6
7
|
List<Country> list; if (param1 != null ){ PageHelper.startPage( 1 , 10 ); list = countryMapper.selectIf(param1); } else { list = new ArrayList<Country>(); } |
這種寫法就能保證安全。
如果你對此不放心,你可以手動清理 ThreadLocal 存儲的分頁參數,可以像下面這樣使用:
1
2
3
4
5
6
7
8
9
10
11
|
List<Country> list; if (param1 != null ){ PageHelper.startPage( 1 , 10 ); try { list = countryMapper.selectAll(); } finally { PageHelper.clearPage(); } } else { list = new ArrayList<Country>(); } |
這么寫很不好看,而且沒有必要。
總結
以上所述是小編給大家介紹的Mybatis分頁插件PageHelper的配置和簡單使用方法(推薦),希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對服務器之家網站的支持!
原文鏈接:http://www.cnblogs.com/kangoroo/p/7998433.html?utm_source=tuicool&utm_medium=referral