摘要:從學(xué)到的不侵入的前言最近搭新項目框架,之前的攔截器都是自己寫的,一般是有個類型做判斷是否增加分頁。發(fā)現(xiàn)開源項目不需要侵入方法的就可以做分頁,特此來源碼分析一下。
從 PageHelper 學(xué)到的不侵入 Signature 的 AOP 前言
最近搭新項目框架,之前 Mybatis 的攔截器都是自己寫的,一般是有個 Page 類型做判斷是否增加分頁 sql。但是這樣同樣的業(yè)務(wù)開放給頁面和 api 可能要寫兩個,一種帶分頁類型 Page 一種不帶分頁。
發(fā)現(xiàn)開源項目 PageHelper 不需要侵入方法的 Signature 就可以做分頁,特此來源碼分析一下。
P.S: 看后面的源碼分析最好能懂 mybatis 得攔截器分頁插件原理
PageHelper 的簡答使用public PageInfoPageHelper.startPage 解析listRpmDetailByCondition(String filename, Date startTime, Date endTime, Integer categoryId, Integer ostypeId, Integer statusId, Integer pageNo, Integer pageSize) { PageHelper.startPage(pageNo, pageSize); List result = rpmDetailMapper.listRpmDetailByCondition(filename, startTime, endTime, categoryId, ostypeId, statusId); return new PageInfo(result); }
public staticPage startPage(int pageNum, int pageSize, boolean count, Boolean reasonable, Boolean pageSizeZero) { Page page = new Page(pageNum, pageSize, count); page.setReasonable(reasonable); page.setPageSizeZero(pageSizeZero); Page oldPage = getLocalPage(); if (oldPage != null && oldPage.isOrderByOnly()) { page.setOrderBy(oldPage.getOrderBy()); } setLocalPage(page); return page; }
protected static final ThreadLocalLOCAL_PAGE = new ThreadLocal(); protected static void setLocalPage(Page page) { LOCAL_PAGE.set(page); }
可以看出在真正使用分頁前,生成了一個 page 對象,然后放入了 ThreadLocal 中,這個思想很巧妙,利用每個請求 Web 服務(wù),每個請求由一個線程處理的原理。利用線程來決定是否進行分頁。
是否用 ThreadLocal 來判斷是否分頁的猜想?//調(diào)用方法判斷是否需要進行分頁,如果不需要,直接返回結(jié)果 if (!dialect.skip(ms, parameter, rowBounds)) { //判斷是否需要進行 count 查詢 if (dialect.beforeCount(ms, parameter, rowBounds)) { //查詢總數(shù) Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql); //處理查詢總數(shù),返回 true 時繼續(xù)分頁查詢,false 時直接返回 if (!dialect.afterCount(count, parameter, rowBounds)) { //當查詢總數(shù)為 0 時,直接返回空的結(jié)果 return dialect.afterPage(new ArrayList(), parameter, rowBounds); } } resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey); } else { //rowBounds用參數(shù)值,不使用分頁插件處理時,仍然支持默認的內(nèi)存分頁 resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); }
@Override private PageParams pageParams; public boolean skip(MappedStatement ms, Object parameterObject, RowBounds rowBounds) { if (ms.getId().endsWith(MSUtils.COUNT)) { throw new RuntimeException("在系統(tǒng)中發(fā)現(xiàn)了多個分頁插件,請檢查系統(tǒng)配置!"); } Page page = pageParams.getPage(parameterObject, rowBounds); if (page == null) { return true; } else { //設(shè)置默認的 count 列 if (StringUtil.isEmpty(page.getCountColumn())) { page.setCountColumn(pageParams.getCountColumn()); } autoDialect.initDelegateDialect(ms); return false; } }
/** * 獲取分頁參數(shù) * * @param parameterObject * @param rowBounds * @return */ public Page getPage(Object parameterObject, RowBounds rowBounds) { Page page = PageHelper.getLocalPage(); if (page == null) { if (rowBounds != RowBounds.DEFAULT) { if (offsetAsPageNum) { page = new Page(rowBounds.getOffset(), rowBounds.getLimit(), rowBoundsWithCount); } else { page = new Page(new int[]{rowBounds.getOffset(), rowBounds.getLimit()}, rowBoundsWithCount); //offsetAsPageNum=false的時候,由于PageNum問題,不能使用reasonable,這里會強制為false page.setReasonable(false); } if(rowBounds instanceof PageRowBounds){ PageRowBounds pageRowBounds = (PageRowBounds)rowBounds; page.setCount(pageRowBounds.getCount() == null || pageRowBounds.getCount()); } } else if(parameterObject instanceof IPage || supportMethodsArguments){ try { page = PageObjectUtil.getPageFromObject(parameterObject, false); } catch (Exception e) { return null; } } if(page == null){ return null; } PageHelper.setLocalPage(page); } //分頁合理化 if (page.getReasonable() == null) { page.setReasonable(reasonable); } //當設(shè)置為true的時候,如果pagesize設(shè)置為0(或RowBounds的limit=0),就不執(zhí)行分頁,返回全部結(jié)果 if (page.getPageSizeZero() == null) { page.setPageSizeZero(pageSizeZero); } return page; }
/** * 獲取 Page 參數(shù) * * @return */ public staticPage getLocalPage() { return LOCAL_PAGE.get(); }
果然如此,至此真相已經(jīng)揭開。
怎么保證之后的 sql 不使用分頁呢?如: 先查出最近一個月注冊的 10 個用戶,再根據(jù)這些用戶查出他們所有的訂單。第一次查詢需要分頁,第二次并不需要
來看看作者怎么實現(xiàn)的?
try{ # 分頁攔截邏輯 } finally { dialect.afterAll(); }
@Override public void afterAll() { //這個方法即使不分頁也會被執(zhí)行,所以要判斷 null AbstractHelperDialect delegate = autoDialect.getDelegate(); if (delegate != null) { delegate.afterAll(); autoDialect.clearDelegate(); } clearPage(); }
/** * 移除本地變量 */ public static void clearPage() { LOCAL_PAGE.remove(); }總結(jié)邏輯
將分頁對象放入 ThreadLocal
根據(jù) ThreadLocal 判斷是否進行分頁
無論分頁與不分頁都需要清除 ThreadLocal
備注com.github.pagehelper pagehelper 5.1.8
文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/74179.html
摘要:閱讀原文造個輪子我學(xué)到了什么聽說的最多的是不是不要重復(fù)的造輪子不要被這句話蒙騙了,這句話應(yīng)該還沒說完整,在什么情況下不要造輪子實際項目中由于工期和質(zhì)量原因,肯定不希望你造輪子,你造輪子花費時間且質(zhì)量不如現(xiàn)有的輪子。 閱讀原文:造個輪子,我學(xué)到了什么 聽說的最多的是不是不要重復(fù)的造輪子?不要被這句話蒙騙了,這句話應(yīng)該還沒說完整,在什么情況下不要造輪子?實際項目中由于工期和質(zhì)量原因,肯定不...
摘要:幾乎每一個接口被調(diào)用后,都要記錄一條跟這個參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。我最終采用了的方式,采取攔截的請求的方式,來記錄日志。所有打上了這個注解的方法,將會記錄日志。那么如何從眾多可能的參數(shù)中,為當前的日志指定對應(yīng)的參數(shù)呢。 前言 不久前,因為需求的原因,需要實現(xiàn)一個操作日志。幾乎每一個接口被調(diào)用后,都要記錄一條跟這個參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。舉個例子,就比如禁言操作,日志中需要記...
摘要:中的裝飾在前端編程中,我們可以采用裝飾器,來實現(xiàn)編程。裝飾器使用我們先建立一個簡單的類,這個類的作用,就是在執(zhí)行的時候,打印出。至此,一個簡單的裝飾器范例已經(jīng)完成。 什么是裝飾器? 了解AOP 在學(xué)習(xí)js中的裝飾器之間,我們需要了解AOP(面向切面編程)編程思想。 AOP是一種可以通過預(yù)編譯方式和運行期動態(tài)代理實現(xiàn)在不修改源代碼的情況下給程序動態(tài)統(tǒng)一添加功能的一種技術(shù)。AOP實際是Go...
摘要:目前只是一個后臺模塊,希望自己技能增強到一定時,可以把的融合進來。目錄第一站,分析了啟動類??匆姏],這個也是配置類,它聲明了視圖解析器地域解析器以及靜態(tài)資源的位置,想起來沒,就是前置,后置。程序啟動類我們點擊源碼看看。 Guns基于SpringBoot,致力于做更簡潔的后臺管理系統(tǒng),完美整合springmvc + shiro + 分頁插件PageHelper + 通用Mapper + ...
摘要:本文首發(fā)于作者最近在學(xué),研究了下和代理模式,寫點心得和大家分享下。所以下面來重點分析下代理模式。這里代理模式分為靜態(tài)代理和動態(tài)代理兩種,我們分別來看下。代理模式,代理,意味著有一方代替另一方完成一件事。 本文首發(fā)于 https://jaychen.cc作者 jaychen 最近在學(xué) Spring,研究了下 AOP 和代理模式,寫點心得和大家分享下。 AOP 先說下AOP,AOP 全稱 ...
閱讀 1020·2021-11-22 13:52
閱讀 1450·2021-11-19 09:40
閱讀 3184·2021-11-16 11:44
閱讀 1279·2021-11-15 11:39
閱讀 3913·2021-10-08 10:04
閱讀 5374·2021-09-22 14:57
閱讀 3108·2021-09-10 10:50
閱讀 3190·2021-08-17 10:13