Mybatis邏輯分頁與物理分頁PageHelper使用解析
一、邏輯分頁
理解:數(shù)據(jù)庫一次性取出全部數(shù)據(jù)存儲到List集合中,再根據(jù)工具類獲取指定返回的數(shù)據(jù),如下是通過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; /** * 當(dāng)前頁數(shù) */ private int currPage; /** * 列表數(shù)據(jù) */ private List<E> list; /** * * @param pageSize 每頁記錄數(shù) * @param currPage 當(dāng)前頁數(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進行查詢。比如在一對多的查詢中,我們有時候是根據(jù)主表進行分頁的,比如說主表有3條,從表有7條,這時候可能出現(xiàn)分頁total數(shù)量為7,這時候我們可以使用嵌套查詢代替聯(lián)表查詢使分頁結(jié)果準(zhǔn)確
②有時候我們需要將查詢出來的數(shù)據(jù)轉(zhuǎn)換為VO對象,但會出現(xiàn)total一直為List.size()的問題,而不是總數(shù)量,這是由于我們查出來的并不是ArrayList對象,而是Page對象,其中封裝了部分參數(shù),當(dāng)調(diào)用PageInfo的構(gòu)造方法時,他并不會進入正常的流程,為了解決這個問題,我們需要手動將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標(biāo)簽中寫入的屬性 * @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相關(guān)配置 * @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(語句處理器) | 負(fù)責(zé)處理 SQL 語句的預(yù)編譯、參數(shù)設(shè)置等工作,其中最核心的工作是創(chuàng)建 JDBC 中的 Statement 對象,并為 SQL 語句綁定參數(shù)。 |
ParameterHandler(參數(shù)處理器) | 用于處理 SQL 語句中的參數(shù),負(fù)責(zé)為 SQL 語句中的參數(shù)設(shè)置值 |
Executor(執(zhí)行器) | 負(fù)責(zé)執(zhí)行由用戶發(fā)起的對數(shù)據(jù)庫的增刪改查操作 |
ResultSetHandler(結(jié)果集處理器) | 負(fù)責(zé)處理 SQL 執(zhí)行后的結(jié)果集,將結(jié)果集轉(zhuǎn)換成用戶需要的 Java 對象 |
3.PageInterceptor攔截器說明
頭部注解及相關(guān)對象作用
@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 | 表示當(dāng)前執(zhí)行的SQL的映射配置信息,包括BoundSql對象 |
Object var2 | 表示傳遞給SQL的參數(shù)對象,可以是單個參數(shù),也可以是一個Map或POJO |
RowBounds var3 | 用于控制結(jié)果集偏移量和限制數(shù)量,即分頁查詢時的偏移量和限制返回的行數(shù) |
ResultHandler var4 | 負(fù)責(zé)處理 SQL 執(zhí)行后的結(jié)果集,將結(jié)果集轉(zhuǎn)換成用戶需要的 Java 對象 |
CacheKey var5 | MyBatis的緩存機制中使用的緩存鍵,可以用于緩存查詢結(jié)果 |
BoundSql var6 | 表示包含了SQL語句和對應(yīng)參數(shù)映射信息的BoundSql對象,可以用于訪問SQL語句及其參數(shù) |
intercept方法源碼解析
public Object intercept(Invocation invocation) throws Throwable { try { // 1.根據(jù)重載的攔截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ù)據(jù)庫進行方言相關(guān)的檢查 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ù)(根據(jù)原語句決定) 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.根據(jù)分頁參數(shù)執(zhí)行語句,在原語句的基礎(chǔ)上添加了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ù)(當(dāng)前頁、頁數(shù)) Page<E> oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page; }
攔截器如何調(diào)用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.他會再次調(diào)用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類,用于封裝分頁信息以及封裝查詢出來的結(jié)果 public class Page<E> extends ArrayList<E>
//1.根據(jù)傳入的Page對象,獲取其中的參數(shù),如total public class PageInfo<T> extends PageSerializable<T> {
總結(jié)流程
PageHelper開啟分頁->將分頁參數(shù)封裝到Page對象中,使用threadlocal存儲->PageInterceptor攔截器對方法進行一次攔截(清除threadlocal里面的分頁參數(shù))->在攔截器中,分別使用兩條SQL語句獲取total以及分頁后的數(shù)據(jù)(count和limit),并將信息封裝給Page對象->新建PageInfo對象,將Page對象傳入,PageInfo對象里面就包含了分頁數(shù)據(jù)及參數(shù)
以上就是Mybatis邏輯分頁與物理分頁PageHelper使用解析的詳細內(nèi)容,更多關(guān)于Mybatis PageHelper分頁的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
基于Spring boot @Value 注解注入屬性值的操作方法
這篇文章主要介紹了結(jié)合SpEL使用@Value-基于配置文件或非配置的文件的值注入-Spring Boot的相關(guān)知識,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-07-07SpringSecurity中的Filter Chain(過濾器鏈)
Spring Security的Filter Chain是由一系列過濾器組成的管道,每個過濾器執(zhí)行特定的安全功能,Spring Security能夠提供強大而靈活的安全控制機制,從而保護你的應(yīng)用程序不受各種網(wǎng)絡(luò)安全威脅的侵害,本文介紹SpringSecurity中的Filter Chain,感興趣的朋友跟隨小編一起看看吧2024-06-06ActiveMQ結(jié)合Spring收發(fā)消息的示例代碼
這篇文章主要介紹了ActiveMQ結(jié)合Spring收發(fā)消息的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-10-10Java使用JSON實現(xiàn)處理中文亂碼和Date格式
這篇文章主要為大家詳細介紹了Java如何在項目中使用JSON實現(xiàn)處理中文亂碼和Date格式的功能,文中的示例代碼講解詳細,需要的小伙伴可以參考一下2023-06-06