Mybatis邏輯分頁與物理分頁PageHelper使用解析
一、邏輯分頁
理解:數(shù)據庫一次性取出全部數(shù)據存儲到List集合中,再根據工具類獲取指定返回的數(shù)據,如下是通過stream流實現(xiàn)
PageUtils
package com.example.segmentfaulttest0.utils;
import lombok.Data;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* @description:分頁工具類
* @author: 袁凱
* @time: 2023/12/14 19:36
*/
@Data
public class PageUtils<E extends Serializable> implements Serializable {
private static final long serialVersionUID = 1L;
/**
* 總記錄數(shù)
*/
private int totalCount;
/**
* 每頁記錄數(shù)
*/
private int pageSize;
/**
* 總頁數(shù)
*/
private int totalPage;
/**
* 當前頁數(shù)
*/
private int currPage;
/**
* 列表數(shù)據
*/
private List<E> list;
/**
*
* @param pageSize 每頁記錄數(shù)
* @param currPage 當前頁數(shù)
* @param totalList 總記錄列表
*/
public PageUtils(int pageSize, int currPage, List<E> totalList) {
this.totalCount = totalList.size();
this.pageSize = pageSize;
this.totalPage = (int) Math.ceil((double) totalCount / pageSize);
this.currPage = currPage;
this.list = this.currPage >= 1 ? totalList.stream().skip((long) (currPage - 1) * pageSize).limit(pageSize).collect(Collectors.toList()) : new ArrayList<>();
}
}二、物理分頁(PageHelper)
1.注意事項
①PageHelper的原理是通過攔截器實現(xiàn)的,他會先發(fā)送一個計數(shù)SQL語句,再使用limit進行查詢。比如在一對多的查詢中,我們有時候是根據主表進行分頁的,比如說主表有3條,從表有7條,這時候可能出現(xiàn)分頁total數(shù)量為7,這時候我們可以使用嵌套查詢代替聯(lián)表查詢使分頁結果準確
②有時候我們需要將查詢出來的數(shù)據轉換為VO對象,但會出現(xiàn)total一直為List.size()的問題,而不是總數(shù)量,這是由于我們查出來的并不是ArrayList對象,而是Page對象,其中封裝了部分參數(shù),當調用PageInfo的構造方法時,他并不會進入正常的流程,為了解決這個問題,我們需要手動將total傳遞給新的PageInfo對象,如下
@GetMapping("/select")
public TableDataInfo selectSelectiveLike() {
startPage();
List<Vrt> vrtList = vrtService.selectSelectiveLike(new Vrt());
PageInfo<Vrt> pageInfo = new PageInfo<>(vrtList);
List<VrtVo> vrtVos = new ArrayList<>();
for (Vrt vrt : vrtList) {
VrtVo vrtVo = new VrtVo();
BeanUtils.copyProperties(vrt, vrtVo);
List<VrtPhone> vrtPhoneList = vrt.getVrtPhoneList();
if (vrtPhoneList != null && !vrtPhoneList.isEmpty()) {
vrtVo.setNum(vrtPhoneList.size());
} else {
vrtVo.setNum(0);
}
vrtVos.add(vrtVo);
}
// 1.由于PageInfo中參數(shù)很多,有時候并不需要,因此自定義了一個TableDataInfo對象用于封裝參數(shù)
// 2.我們在這里手動將PageInfo的值傳遞給VO的分頁對象即可
TableDataInfo tableDataInfo = new TableDataInfo();
tableDataInfo.setCode(HttpStatus.SUCCESS);
tableDataInfo.setRows(vrtVos);
tableDataInfo.setMsg("查詢成功");
tableDataInfo.setTotal(pageInfo.getTotal());
return tableDataInfo;
}2.mybatis簡要插件實現(xiàn)
插件類
package com.example.segmentfaulttest0.Interceptor;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.executor.resultset.ResultSetHandler;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
import java.sql.Statement;
import java.util.Properties;
/**
* @description:自定義插件,用于攔截mybatis的query方法
* @author: 袁凱
* @time: 2023/12/18 16:53
*/
//mybatis的攔截器注解以及簽名注解,用于需要攔截的類名,方法名,參數(shù)類型
@Intercepts({//注意看這個大花括號,也就這說這里可以定義多個@Signature對多個地方攔截,都用這個攔截器
@Signature(type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})
//@Intercepts({
// @Signature(
// type = Executor.class,
// method = "update",
// args = {MappedStatement.class, Object.class}),
//})
public class MyPlugin implements Interceptor {
public MyPlugin() {
System.out.println("myplugin");
}
@Override
public Object intercept(Invocation invocation) throws Throwable {
//攔截成功,do something
System.out.println("do something");
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this); //將被攔截對象生成代理對象
}
/**
* 用于獲取pom.xml中property標簽中寫入的屬性
* @param properties
*/
@Override
public void setProperties(Properties properties) {
Interceptor.super.setProperties(properties);
}
}配置類
package com.example.segmentfaulttest0.config;
import com.example.segmentfaulttest0.Interceptor.MyPlugin;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
/**
* @description:Mybatis相關配置
* @author: 袁凱
* @time: 2023/12/18 17:39
*/
@Configuration
public class MybatisConfig {
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
@Bean
public void myPlugin() {
for (SqlSessionFactory sqlSessionFactory : sqlSessionFactoryList) {
org.apache.ibatis.session.Configuration configuration = sqlSessionFactory.getConfiguration();
// 最后添加的會更早執(zhí)行
configuration.addInterceptor(new MyPlugin());
}
}
}mybatis可攔截的類
| 對象 | 作用 |
|---|---|
| StatementHandler(語句處理器) | 負責處理 SQL 語句的預編譯、參數(shù)設置等工作,其中最核心的工作是創(chuàng)建 JDBC 中的 Statement 對象,并為 SQL 語句綁定參數(shù)。 |
| ParameterHandler(參數(shù)處理器) | 用于處理 SQL 語句中的參數(shù),負責為 SQL 語句中的參數(shù)設置值 |
| Executor(執(zhí)行器) | 負責執(zhí)行由用戶發(fā)起的對數(shù)據庫的增刪改查操作 |
| ResultSetHandler(結果集處理器) | 負責處理 SQL 執(zhí)行后的結果集,將結果集轉換成用戶需要的 Java 對象 |
3.PageInterceptor攔截器說明
頭部注解及相關對象作用
@Intercepts({@Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}
), @Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})
public class PageInterceptor implements Interceptor {| 表列 A | 表列 B |
|---|---|
| MappedStatement var1 | 表示當前執(zhí)行的SQL的映射配置信息,包括BoundSql對象 |
| Object var2 | 表示傳遞給SQL的參數(shù)對象,可以是單個參數(shù),也可以是一個Map或POJO |
| RowBounds var3 | 用于控制結果集偏移量和限制數(shù)量,即分頁查詢時的偏移量和限制返回的行數(shù) |
| ResultHandler var4 | 負責處理 SQL 執(zhí)行后的結果集,將結果集轉換成用戶需要的 Java 對象 |
| CacheKey var5 | MyBatis的緩存機制中使用的緩存鍵,可以用于緩存查詢結果 |
| BoundSql var6 | 表示包含了SQL語句和對應參數(shù)映射信息的BoundSql對象,可以用于訪問SQL語句及其參數(shù) |
intercept方法源碼解析
public Object intercept(Invocation invocation) throws Throwable {
try {
// 1.根據重載的攔截query方法不同,獲取不同的對象以及緩存key
Object[] args = invocation.getArgs();
MappedStatement ms = (MappedStatement)args[0];
Object parameter = args[1];
RowBounds rowBounds = (RowBounds)args[2];
ResultHandler resultHandler = (ResultHandler)args[3];
Executor executor = (Executor)invocation.getTarget();
CacheKey cacheKey;
BoundSql boundSql;
if (args.length == 4) {
boundSql = ms.getBoundSql(parameter);
cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
} else {
cacheKey = (CacheKey)args[4];
boundSql = (BoundSql)args[5];
}
// 2.mybatis對不同的數(shù)據庫進行方言相關的檢查
this.checkDialectExists();
if (this.dialect instanceof BoundSqlInterceptor.Chain) {
boundSql = ((BoundSqlInterceptor.Chain)this.dialect).doBoundSql(Type.ORIGINAL, boundSql, cacheKey);
}
List resultList;
if (!this.dialect.skip(ms, parameter, rowBounds)) {
this.debugStackTraceLog();
if (this.dialect.beforeCount(ms, parameter, rowBounds)) {
// 3.執(zhí)行一條sql,select count(*) from xxx獲取總條數(shù)(根據原語句決定)
Long count = this.count(executor, ms, parameter, rowBounds, (ResultHandler)null, boundSql);
if (!this.dialect.afterCount(count, parameter, rowBounds)) {
Object var12 = this.dialect.afterPage(new ArrayList(), parameter, rowBounds);
return var12;
}
}
// 4.根據分頁參數(shù)執(zhí)行語句,在原語句的基礎上添加了limit ?,?
resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
} else {
resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
}
Object var16 = this.dialect.afterPage(resultList, parameter, rowBounds);
return var16;
} finally {
if (this.dialect != null) {
this.dialect.afterAll();
}
}
}PageHelper類中startPage(pageNum,pageSize)的作用
public static <E> Page<E> startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) {
Page<E> page = new Page(pageNum, pageSize, count);
page.setReasonable(reasonable);
page.setPageSizeZero(pageSizeZero);
// 1.使用threadlocal進行線程存儲,為每個線程保存每個 分頁參數(shù)(當前頁、頁數(shù))
Page<E> oldPage = getLocalPage();
if (oldPage != null && oldPage.isOrderByOnly()) {
page.setOrderBy(oldPage.getOrderBy());
}
setLocalPage(page);
return page;
}攔截器如何調用threadlocal里面的分頁參數(shù)
在上面的intercept方法第四點中,他會進入ExecutorUtil的方法
public static <E> List<E> pageQuery(Dialect dialect, Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql, CacheKey cacheKey) throws SQLException {
if (!dialect.beforePage(ms, parameter, rowBounds)) {
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, boundSql);
} else {
parameter = dialect.processParameterObject(ms, parameter, boundSql, cacheKey);
String pageSql = dialect.getPageSql(ms, boundSql, parameter, rowBounds, cacheKey);
BoundSql pageBoundSql = new BoundSql(ms.getConfiguration(), pageSql, boundSql.getParameterMappings(), parameter);
Map<String, Object> additionalParameters = getAdditionalParameter(boundSql);
Iterator var12 = additionalParameters.keySet().iterator();
while(var12.hasNext()) {
String key = (String)var12.next();
pageBoundSql.setAdditionalParameter(key, additionalParameters.get(key));
}
if (dialect instanceof BoundSqlInterceptor.Chain) {
// 1.他會再次調用PageHelper類里面的threadlocal,并獲取里面的分頁參數(shù)
pageBoundSql = ((BoundSqlInterceptor.Chain)dialect).doBoundSql(Type.PAGE_SQL, pageBoundSql, cacheKey);
}
return executor.query(ms, parameter, RowBounds.DEFAULT, resultHandler, cacheKey, pageBoundSql);
}
}// 回到pageHelper類中,他會將分頁參數(shù)封裝成Page對象,再放入threadlocal中
public BoundSql doBoundSql(BoundSqlInterceptor.Type type, BoundSql boundSql, CacheKey cacheKey) {
Page<Object> localPage = getLocalPage();
BoundSqlInterceptor.Chain chain = localPage != null ? localPage.getChain() : null;
if (chain == null) {
BoundSqlInterceptor boundSqlInterceptor = localPage != null ? localPage.getBoundSqlInterceptor() : null;
BoundSqlInterceptor.Chain defaultChain = this.pageBoundSqlInterceptors != null ? this.pageBoundSqlInterceptors.getChain() : null;
if (boundSqlInterceptor != null) {
chain = new BoundSqlInterceptorChain(defaultChain, Arrays.asList(boundSqlInterceptor));
} else if (defaultChain != null) {
chain = defaultChain;
}
if (chain == null) {
chain = DO_NOTHING;
}
if (localPage != null) {
localPage.setChain((BoundSqlInterceptor.Chain)chain);
}
}
return ((BoundSqlInterceptor.Chain)chain).doBoundSql(type, boundSql, cacheKey);
}Page對象(實際上返回的對象)與PageInfo對象(我們最終使用的對象)
//1.集成了ArrayList類,用于封裝分頁信息以及封裝查詢出來的結果 public class Page<E> extends ArrayList<E>
//1.根據傳入的Page對象,獲取其中的參數(shù),如total
public class PageInfo<T> extends PageSerializable<T> {總結流程
PageHelper開啟分頁->將分頁參數(shù)封裝到Page對象中,使用threadlocal存儲->PageInterceptor攔截器對方法進行一次攔截(清除threadlocal里面的分頁參數(shù))->在攔截器中,分別使用兩條SQL語句獲取total以及分頁后的數(shù)據(count和limit),并將信息封裝給Page對象->新建PageInfo對象,將Page對象傳入,PageInfo對象里面就包含了分頁數(shù)據及參數(shù)
以上就是Mybatis邏輯分頁與物理分頁PageHelper使用解析的詳細內容,更多關于Mybatis PageHelper分頁的資料請關注腳本之家其它相關文章!
相關文章
java輸出1~100之間的全部素數(shù)的5種方式總結
這篇文章主要介紹了java輸出1~100之間的全部素數(shù)的5種方式總結,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-02-02
mybatis實現(xiàn)遍歷Map的key和value
這篇文章主要介紹了mybatis實現(xiàn)遍歷Map的key和value方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-01-01
Spring Boot與RabbitMQ結合實現(xiàn)延遲隊列的示例
本篇文章主要介紹了Spring Boot與RabbitMQ結合實現(xiàn)延遲隊列的示例,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-11-11
@Scheduled fixedDelayString 加載properties配置方式
這篇文章主要介紹了@Scheduled fixedDelayString 加載properties配置方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
Spring Boot項目中Maven編譯參數(shù)source、target與release的區(qū)別及配置實踐方案
在Spring Boot項目開發(fā)中,將本地代碼打包部署到服務器時,常會遇到UnsupportedClassVersionError或NoSuchMethodError等異常,本文將直觀解析這些配置的區(qū)別,并提供標準的配置方案,感興趣的朋友跟隨小編一起看看吧2026-02-02

