第一種:LIMIT關鍵字
1,mapper代碼
select * from tb_user limit #{pageNo}, #{pageSize}
2,業務層直接調用
public List findByPageInfo(PageInfo info) {
return userMapper.selectByPageInfo(info);
}
3,優點
靈活性高,可優化空間大
mysql分頁語句優化
4,缺點
實現複雜。
第二種:RowBounds實現分頁
Mybatis提供RowBounds類來實現邏輯分頁。RowBounds中有2個字段offset和limit。這種方式獲取所有的ResultSet,從ResultSet中的offset位置開始獲取limit個記錄。但這並不意味着JDBC驅動器會將所有的ResultSet存放在內存,實際上只加載小部分數據到內存,如果需要,再加載部分數據到內存。
1,mapper的xml代碼
select * from user
2, dao代碼
List<User> selectPage(RowBounds rowBounds);
3, 分頁查詢
List<User> users = userMapper.selectPage(new RowBounds(5, 10));
log.info("users:{}",users);
查詢結果:
users:[User(id=6, username=柳雲璇, grade=小三(5)班, age=25, phone=17358053274, sex=女), User(id=7, username=酆雨寒, grade=高一(5)班, age=19, phone=15394214112, sex=女), User(id=8, username=鄭春陽, grade=小三(7)班, age=24, phone=15004202411, sex=男)]
4, 優點
使用起來比直接limit簡單。
5,缺點
DB壓力比較大,因爲將結果暫存在db了。
第三種:藉助數組進行分頁(和上面的很相似,只是查詢出所有,自己通過subList()進行返回)
1,mapper接口代碼
List<Student> queryStudentsByArray();
2,mapper的xml代碼
<select id="queryStudentsByArray" resultMap="studentmapper">
select * from student
</select>
3,定義IStuService接口,並且定義分頁方法:
List<Student> queryStudentsByArray(int currPage, int pageSize);
通過接收currPage參數表示顯示第幾頁的數據,pageSize表示每頁顯示的數據條數。
創建IStuService接口實現類StuServiceIml對方法進行實現,對獲取到的數組通過currPage和pageSize進行分頁:
@Override
public List<Student> queryStudentsByArray(int currPage, int pageSize) {
List<Student> students = studentMapper.queryStudentsByArray();
// 從第幾條數據開始
int firstIndex = (currPage - 1) * pageSize;
// 到第幾條數據結束
int lastIndex = currPage * pageSize;
return students.subList(firstIndex, lastIndex);
}
通過subList方法,獲取到兩個索引間的所有數據。
4,最後在controller中創建測試方法:
@ResponseBody
@RequestMapping("/student/array/{currPage}/{pageSize}")
public List<Student> getStudentByArray(@PathVariable("currPage") int currPage, @PathVariable("pageSize") int pageSize) {
List<Student> student = StuServiceIml.queryStudentsByArray(currPage, pageSize);
return student;
}
通過用戶傳入的currPage和pageSize獲取指定數據。
他的有點和劣勢與上面第中方法類似。
第四種:Interceptor 實現
1,自定義Interceptor
@Intercepts({@Signature(type = StatementHandler.class, method = “prepare”, args = {Connection.class, Integer.class})})
public class DefinedPageInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
//獲取StatementHandler,默認的是RoutingStatementHandler
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
//獲取StatementHandler的包裝類
MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
//分隔代理對象
while (metaObject.hasGetter("h")) {
Object obj = metaObject.getValue("h");
metaObject = SystemMetaObject.forObject(obj);
}
while (metaObject.hasGetter("target")) {
Object obj = metaObject.getValue("target");
metaObject = SystemMetaObject.forObject(obj);
}
//獲取查看接口映射的相關信息
MappedStatement mappedStatement = (MappedStatement) metaObject.getValue("delegate.mappedStatement");
String mapId = mappedStatement.getId();
//攔截以ByInterceptor結尾的請求,統一實現分頁
if (mapId.matches(".+ByInterceptor$")) {
System.out.println("LOG:已觸發分頁攔截器");
//獲取進行數據庫操作時管理參數的Handler
ParameterHandler parameterHandler = (ParameterHandler) metaObject.getValue("delegate.parameterHandler");
// 分頁對象需要自己構建,到時候分頁方法中要傳入
//獲取請求時的參數
PageInfo info = (PageInfo) parameterHandler.getParameterObject();
//獲取原始SQL語句
String originalSql = (String) metaObject.getValue("delegate.boundSql.sql");
//構建分頁功能的SQL語句
String sql = originalSql.trim() + " limit " + info.getPageNum() + ", " + info.getPageSize();
metaObject.setValue("delegate.boundSql.sql", sql);
}
//調用原對象方法,進入責任鏈下一級
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
//生成Object對象的動態代理對象
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
//如果分頁每頁數量是統一的,可以在這裏進行統一配置,也就無需再傳入PageInfo信息了
}
}
2,添加到mybatis中
@Bean(“oneSqlSessionFactory”)
public SqlSessionFactory sqlSessionFactory() {
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
try {
sqlSessionFactoryBean.setMapperLocations(
// 設置mybatis的xml所在位置
new PathMatchingResourcePatternResolver().getResources(“classpath*:mapping/one/*Mapper.xml”));
//設置自定義的插件
sqlSessionFactoryBean.setPlugins(new DefinedPageInterceptor());
return sqlSessionFactoryBean.getObject();
} catch (Exception e) {
return null;
}
}
3,mapper方法
/**
* Interceptor 實現分頁,必須以ByInterceptor結構,自定義的Interceptor才能
* 識別出來,並且必須傳入PageInfo
* @param pageInfo 自定義的分頁
* @return
*/
List selectPageByInterceptor(PageInfo pageInfo);
xml文件
<select id="selectPageByInterceptor" resultType="com.example.demo.mapper.one.User">
select * from user
</select>
4, 運行
/**
* 自定義PageInterceptor分頁
*/
@Test
public void test04() {
PageInfo pageInfo=new PageInfo();
pageInfo.setPageNum(5);
pageInfo.setPageSize(5);
List users = userMapper.selectPageByInterceptor(pageInfo);
log.info(“users:{}”,users);
}
攔截更改後的sql。
5, 運行結果
users:[User(id=6, username=柳雲璇, grade=小三(5)班, age=25, phone=17358053274, sex=女), User(id=7, username=酆雨寒, grade=高一(5)班, age=19, phone=15394214112, sex=女), User(id=8, username=鄭春陽, grade=小三(7)班, age=24, phone=15004202411, sex=男)]
第五種:PageHelper (他其實也是第4種的一個實現,用的多,就把他單獨描述爲一種)
1,官網
官網:https://pagehelper.github.io/
使用方式:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/wikis/zh/HowToUse.md
2, 添加pom依賴
com.github.pagehelper pagehelper 最新版本
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.2</version>
</dependency>
或者
<!-- https://mvnrepository.com/artifact/com.github.pagehelper/pagehelper-spring-boot-starter -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.4.6</version>
</dependency>
特別注意,分頁和查詢直接不能有其他語句
要絕對保證PageHelper.startPage和分頁查詢語句之間不要有任何其他語句,或者在程序結束時增加PageHelper.clearPage();的調用
3, 使用
//第一種,RowBounds方式的調用
List<User> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));
//第二種,Mapper接口方式的調用,推薦這種使用方式。
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectIf(1);
//第三種,Mapper接口方式的調用,推薦這種使用方式。
PageHelper.offsetPage(1, 10);
List<User> list = userMapper.selectIf(1);
//第四種,參數方法調用
//存在以下 Mapper 接口方法,你不需要在 xml 處理後兩個參數
public interface CountryMapper {
List<User> selectByPageNumSize(
@Param("user") User user,
@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代碼中直接調用:
List<User> list = userMapper.selectByPageNumSize(user, 1, 10);
//第五種,參數對象
//如果 pageNum 和 pageSize 存在於 User 對象中,只要參數有值,也會被分頁
//有如下 User 對象
public class User {
//其他fields
//下面兩個參數名和 params 配置的名字一致
private Integer pageNum;
private Integer pageSize;
}
//存在以下 Mapper 接口方法,你不需要在 xml 處理後兩個參數
public interface CountryMapper {
List<User> selectByPageNumSize(User user);
}
//當 user 中的 pageNum!= null && pageSize!= null 時,會自動分頁
List<User> list = userMapper.selectByPageNumSize(user);
//第六種,ISelect 接口方式
//jdk6,7用法,創建接口
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
@Override
public void doSelect() {
userMapper.selectGroupBy();
}
});
//jdk8 lambda用法
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy());
//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
@Override
public void doSelect() {
userMapper.selectGroupBy();
}
});
//對應的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> userMapper.selectGroupBy());
//count查詢,返回一個查詢語句的count數
long total = PageHelper.count(new ISelect() {
@Override
public void doSelect() {
userMapper.selectLike(user);
}
});
//lambda
total = PageHelper.count(()->userMapper.selectLike(user));
3). 在 Spring Boot 中配置
Spring Boot 引入 starter 後自動生效,對分頁插件進行配置時,在 Spring Boot 對應的配置文件 application.[properties|yaml]
中配置:
properties:
pagehelper.propertyName=propertyValue
pagehelper.reasonable=false
pagehelper.defaultCount=true
yaml:
pagehelper:
propertyName: propertyValue
reasonable: false
defaultCount: true # 分頁插件默認參數支持 default-count 形式,自定義擴展的參數,必須大小寫一致
支持的默認參數參考: PageHelperStandardProperties.java
4). 分頁插件banner設置
爲了避免多次配置分頁插件導致的錯誤,配置分頁插件後,啓動時會輸出 banner。
DEBUG [main] -
,------. ,--. ,--. ,--.
| .--. ' ,--,--. ,---. ,---. | '--' | ,---. | | ,---. ,---. ,--.--.
| '--' | ' ,-. | | .-. | | .-. : | .--. | | .-. : | | | .-. | | .-. : | .--'
| | --' \ '-' | ' '-' ' \ --. | | | | \ --. | | | '-' ' \ --. | |
`--' `--`--' .`- / `----' `--' `--' `----' `--' | |-' `----' `--'
`---' `--' is intercepting.
如果在項目啓動時輸出了多次 banner,就是配置了多次分頁插件,根據日誌輸出的位置排查系統通過哪些方式配置了分頁插件。
如果不想在啓動時輸出 banner,可以通過系統變量或環境變量關閉。
- 系統變量:
-Dpagehelper.banner=false
- 環境變量:
PAGEHELPER_BANNER=false
如何在代碼中使用
分頁插件支持以下幾種調用方式:
//第一種,RowBounds方式的調用
List<User> list = sqlSession.selectList("x.y.selectIf", null, new RowBounds(0, 10));
//第二種,Mapper接口方式的調用,推薦這種使用方式。
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectIf(1);
//第三種,Mapper接口方式的調用,推薦這種使用方式。
PageHelper.offsetPage(1, 10);
List<User> list = userMapper.selectIf(1);
//第四種,參數方法調用
//存在以下 Mapper 接口方法,你不需要在 xml 處理後兩個參數
public interface CountryMapper {
List<User> selectByPageNumSize(
@Param("user") User user,
@Param("pageNum") int pageNum,
@Param("pageSize") int pageSize);
}
//配置supportMethodsArguments=true
//在代碼中直接調用:
List<User> list = userMapper.selectByPageNumSize(user, 1, 10);
//第五種,參數對象
//如果 pageNum 和 pageSize 存在於 User 對象中,只要參數有值,也會被分頁
//有如下 User 對象
public class User {
//其他fields
//下面兩個參數名和 params 配置的名字一致
private Integer pageNum;
private Integer pageSize;
}
//存在以下 Mapper 接口方法,你不需要在 xml 處理後兩個參數
public interface CountryMapper {
List<User> selectByPageNumSize(User user);
}
//當 user 中的 pageNum!= null && pageSize!= null 時,會自動分頁
List<User> list = userMapper.selectByPageNumSize(user);
//第六種,ISelect 接口方式
//jdk6,7用法,創建接口
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(new ISelect() {
@Override
public void doSelect() {
userMapper.selectGroupBy();
}
});
//jdk8 lambda用法
Page<User> page = PageHelper.startPage(1, 10).doSelectPage(()-> userMapper.selectGroupBy());
//也可以直接返回PageInfo,注意doSelectPageInfo方法和doSelectPage
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(new ISelect() {
@Override
public void doSelect() {
userMapper.selectGroupBy();
}
});
//對應的lambda用法
pageInfo = PageHelper.startPage(1, 10).doSelectPageInfo(() -> userMapper.selectGroupBy());
//count查詢,返回一個查詢語句的count數
long total = PageHelper.count(new ISelect() {
@Override
public void doSelect() {
userMapper.selectLike(user);
}
});
//lambda
total=PageHelper.count(()->userMapper.selectLike(user));
下面對最常用的方式進行詳細介紹
(一)、RowBounds方式的調用
List<User> list=sqlSession.selectList("x.y.selectIf",null,new RowBounds(1,10));
使用這種調用方式時,你可以使用RowBounds參數進行分頁,這種方式侵入性最小,我們可以看到,通過RowBounds方式調用只是使用了這個參數,並沒有增加其他任何內容。
分頁插件檢測到使用了RowBounds參數時,就會對該查詢進行物理分頁。
關於這種方式的調用,有兩個特殊的參數是針對 RowBounds
的,你可以參看上面的 場景一 和 場景二
注:不只有命名空間方式可以用RowBounds,使用接口的時候也可以增加RowBounds參數,例如:
//這種情況下也會進行物理分頁查詢 List<User> selectAll(RowBounds rowBounds);
注意: 由於默認情況下的 RowBounds
無法獲取查詢總數,分頁插件提供了一個繼承自 RowBounds
的 PageRowBounds
,這個對象中增加了 total
屬性,執行分頁查詢後,可以從該屬性得到查詢總數。
(二)、PageHelper.startPage靜態方法調用
除了 PageHelper.startPage
方法外,還提供了類似用法的 PageHelper.offsetPage
方法。
在你需要進行分頁的 MyBatis 查詢方法前調用 PageHelper.startPage
靜態方法即可,緊跟在這個方法後的第一個MyBatis 查詢方法會被進行分頁。
例一:
//獲取第1頁,10條內容,默認查詢總數count PageHelper.startPage(1, 10); //緊跟着的第一個select方法會被分頁 List<User> list = userMapper.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 參數 PageHelper.startPage(request); //緊跟着的第一個select方法會被分頁 List<User> list = userMapper.selectIf(1); //後面的不會被分頁,除非再次調用PageHelper.startPage List<User> list2 = userMapper.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條內容,默認查詢總數count PageHelper.startPage(1, 10); List<User> list = userMapper.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());
(三)、使用參數方式
想要使用參數方式,需要配置 supportMethodsArguments
參數爲 true
,同時要配置 params
參數。 例如下面的配置:
<plugins> <!-- com.github.pagehelper爲PageHelper類所在包名 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 使用下面的方式配置參數,後面會有所有的參數介紹 --> <property name="supportMethodsArguments" value="true"/> <property name="params" value="pageNum=pageNumKey;pageSize=pageSizeKey;"/> </plugin> </plugins>
在 MyBatis 方法中:
List<User> selectByPageNumSize( @Param("user") User user, @Param("pageNumKey") int pageNum, @Param("pageSizeKey") int pageSize);
當調用這個方法時,由於同時發現了 pageNumKey
和 pageSizeKey
參數,這個方法就會被分頁。params 提供的幾個參數都可以這樣使用。
除了上面這種方式外,如果 User 對象中包含這兩個參數值,也可以有下面的方法:
List<User> selectByPageNumSize(User user);
當從 User 中同時發現了 pageNumKey
和 pageSizeKey
參數,這個方法就會被分頁。
注意:pageNum
和 pageSize
兩個屬性同時存在纔會觸發分頁操作,在這個前提下,其他的分頁參數纔會生效。
(四)、PageHelper安全調用
1. 使用 RowBounds
和 PageRowBounds
參數方式是極其安全的
2. 使用參數方式是極其安全的
3. 使用 ISelect 接口調用是極其安全的
ISelect 接口方式除了可以保證安全外,還特別實現了將查詢轉換爲單純的 count 查詢方式,這個方法可以將任意的查詢方法,變成一個 select count(*)
的查詢方法。
4. 什麼時候會導致不安全的分頁?
PageHelper
方法使用了靜態的 ThreadLocal
參數,分頁參數和線程是綁定的。
只要你可以保證在 PageHelper
方法調用後緊跟 MyBatis 查詢方法,這就是安全的。因爲 PageHelper
在 finally
代碼段中自動清除了 ThreadLocal
存儲的對象。
如果代碼在進入 Executor
前發生異常,就會導致線程不可用,這屬於人爲的 Bug(例如接口方法和 XML 中的不匹配,導致找不到 MappedStatement
時), 這種情況由於線程不可用,也不會導致 ThreadLocal
參數被錯誤的使用。
但是如果你寫出下面這樣的代碼,就是不安全的用法:
PageHelper.startPage(1, 10); List<User> list; if(param1 != null){ list = userMapper.selectIf(param1); } else { list = new ArrayList<User>(); }
這種情況下由於 param1 存在 null 的情況,就會導致 PageHelper 生產了一個分頁參數,但是沒有被消費,這個參數就會一直保留在這個線程上。當這個線程再次被使用時,就可能導致不該分頁的方法去消費這個分頁參數,這就產生了莫名其妙的分頁。
上面這個代碼,應該寫成下面這個樣子:
List<User> list; if(param1 != null){ PageHelper.startPage(1, 10); list = userMapper.selectIf(param1); } else { list = new ArrayList<User>(); }
這種寫法就能保證安全。
如果你對此不放心,你可以手動清理 ThreadLocal
存儲的分頁參數,可以像下面這樣使用:
List<User> list; if(param1 != null){ PageHelper.startPage(1, 10); try{ list = userMapper.selectAll(); } finally { PageHelper.clearPage(); } } else { list = new ArrayList<User>(); }
這麼寫很不好看,而且沒有必要。