<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
在最近的專案開發中遇到一個需求 需要對mysql做一些慢查詢、大結果集等異常指標進行收集監控,從運維角度並沒有對mysql進行統一的指標蒐集,所以需要通過程式碼層面對指標進行收集,我採用的方法是通過mybatis的Interceptor攔截器進行指標收集在開發中出現了自定義攔截器 對於查詢無法進行攔截的問題幾經周折後終於解決,故進行記錄學習,分享給大家下次遇到少走一些彎路;
像springmvc一樣,mybatis也提供了攔截器實現,對Executor、StatementHandler、ResultSetHandler、ParameterHandler提供了攔截器功能。
在使用時我們只需要 implements org.apache.ibatis.plugin.Interceptor類實現 方法頭標註相應註解即可 如下程式碼會對CRUD的操作進行攔截:
@Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) })
攔截的類(type) | 攔截的方法(method) |
---|---|
Executor | update, query, flushStatements, commit, rollback,getTransaction, close, isClosed |
ParameterHandler | getParameterObject, setParameters |
StatementHandler | prepare, parameterize, batch, update, query |
ResultSetHandler | handleResultSets, handleOutputParameters |
官方程式碼範例:
@Intercepts({@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})}) public class TestInterceptor implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); //被代理物件 Method method = invocation.getMethod(); //代理方法 Object[] args = invocation.getArgs(); //方法引數 // do something ...... 方法攔截前執行程式碼塊 Object result = invocation.proceed(); // do something .......方法攔截後執行程式碼塊 return result; } public Object plugin(Object target) { return Plugin.wrap(target, this); } }
因為mybatis框架本身就是一個可以獨立使用的框架,沒有像Spring這種做了很多的依賴注入。 如果我們的攔截器需要一些變數物件,而且這個物件是支援可設定的。
類似於Spring中的@Value("${}")從application.properties檔案中獲取。
使用方法:
mybatis-config.xml設定:
<plugin interceptor="com.plugin.mybatis.MyInterceptor"> <property name="username" value="xxx"/> <property name="password" value="xxx"/> </plugin>
方法中獲取引數:properties.getProperty("username");
update型別操作可以正常攔截 query型別查詢sql無法進入自定義攔截器,導致攔截失敗以下為部分原始碼 由於涉及到公司程式碼以下程式碼做了mask的處理
@Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}) }) public class SQLInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { ..... } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { ..... } }
自定義攔截器攔截的是Executor執行器4引數query方法和update型別方法 由於mybatis的攔截器為責任鏈模式呼叫有一個傳遞機制 (第一個攔截器執行完向下一個攔截器傳遞 具體實現可以看一下原始碼)
update的操作執行確實進了自定義攔截器但是查詢的操作始終進不來後通過追蹤原始碼發現
pagehelper外掛的 PageInterceptor 攔截器 會對 Executor執行器method=query 的4引數方法進行修改轉化為 6引數方法 向下傳遞 導致執行順序在pagehelper後面的攔截器的Executor執行器4引數query方法不會接收到傳遞過來的請求導致攔截器失效
/** * Mybatis - 通用分頁攔截器 * <p> * GitHub: https://github.com/pagehelper/Mybatis-PageHelper * <p> * Gitee : https://gitee.com/free/Mybatis_PageHelper * * @author liuzh/abel533/isea533 * @version 5.0.0 */ @SuppressWarnings({"rawtypes", "unchecked"}) @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 { private volatile Dialect dialect; private String countSuffix = "_COUNT"; protected Cache<String, MappedStatement> msCountMap = null; private String default_dialect_class = "com.github.pagehelper.PageHelper"; @Override public Object intercept(Invocation invocation) throws Throwable { try { 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) { //4 個引數時 boundSql = ms.getBoundSql(parameter); cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql); } else { //6 個引數時 cacheKey = (CacheKey) args[4]; boundSql = (BoundSql) args[5]; } checkDialectExists(); List resultList; //呼叫方法判斷是否需要進行分頁,如果不需要,直接返回結果 if (!dialect.skip(ms, parameter, rowBounds)) { //判斷是否需要進行 count 查詢 if (dialect.beforeCount(ms, parameter, rowBounds)) { //查詢總數 Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql); //處理查詢總數,返回 true 時繼續分頁查詢,false 時直接返回 if (!dialect.afterCount(count, parameter, rowBounds)) { //當查詢總數為 0 時,直接返回空的結果 return dialect.afterPage(new ArrayList(), parameter, rowBounds); } } resultList = ExecutorUtil.pageQuery(dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey); } else { //rowBounds用引數值,不使用分頁外掛處理時,仍然支援預設的記憶體分頁 resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql); } return dialect.afterPage(resultList, parameter, rowBounds); } finally { if(dialect != null){ dialect.afterAll(); } } } /** * Spring bean 方式設定時,如果沒有設定屬性就不會執行下面的 setProperties 方法,就不會初始化 * <p> * 因此這裡會出現 null 的情況 fixed #26 */ private void checkDialectExists() { if (dialect == null) { synchronized (default_dialect_class) { if (dialect == null) { setProperties(new Properties()); } } } } private Long count(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { String countMsId = ms.getId() + countSuffix; Long count; //先判斷是否存在手寫的 count 查詢 MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId); if (countMs != null) { count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler); } else { countMs = msCountMap.get(countMsId); //自動建立 if (countMs == null) { //根據當前的 ms 建立一個返回值為 Long 型別的 ms countMs = MSUtils.newCountMappedStatement(ms, countMsId); msCountMap.put(countMsId, countMs); } count = ExecutorUtil.executeAutoCount(dialect, executor, countMs, parameter, boundSql, rowBounds, resultHandler); } return count; } @Override public Object plugin(Object target) { return Plugin.wrap(target, this); } @Override public void setProperties(Properties properties) { //快取 count ms msCountMap = CacheFactory.createCache(properties.getProperty("msCountCache"), "ms", properties); String dialectClass = properties.getProperty("dialect"); if (StringUtil.isEmpty(dialectClass)) { dialectClass = default_dialect_class; } try { Class<?> aClass = Class.forName(dialectClass); dialect = (Dialect) aClass.newInstance(); } catch (Exception e) { throw new PageException(e); } dialect.setProperties(properties); String countSuffix = properties.getProperty("countSuffix"); if (StringUtil.isNotEmpty(countSuffix)) { this.countSuffix = countSuffix; } } } }
通過上述我們定位到了問題產生的原因 解決起來就簡單多了 有倆個方案如下:
mybatis-config.xml 程式碼
我們的自定義攔截器設定的執行順序是在PageInterceptor這個攔截器前面的(先設定後執行)
<plugins> <!-- com.github.pagehelper為PageHelper類所在包名 --> <plugin interceptor="com.github.pagehelper.PageInterceptor"> <!-- 使用下面的方式設定引數,後面會有所有的引數介紹 --> <!-- reasonable:分頁合理化引數,預設值為false。當該引數設定為 true 時,pageNum<=0 時會查詢第一頁, pageNum>pages(超過總數時),會查詢最後一頁。預設false 時,直接根據引數進行查詢。--> <property name="reasonable" value="true"/> <!-- supportMethodsArguments:支援通過 Mapper 介面引數來傳遞分頁引數,預設值false,分頁外掛會從查詢方法的引數值中,自動根據上面 params 設定的欄位中取值,查詢到合適的值時就會自動分頁。 使用方法可以參考測試程式碼中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和 ArgumentsObjTest。--> <property name="supportMethodsArguments" value="true"/> <!-- autoRuntimeDialect:預設值為 false。設定為 true 時,允許在執行時根據多資料來源自動識別對應方言的分頁 (不支援自動選擇sqlserver2012,只能使用sqlserver),用法和注意事項參考下面的場景五--> <property name="autoRuntimeDialect" value="true"/> <!-- params:為了支援startPage(Object params)方法,增加了該引數來設定引數對映,用於從物件中根據屬性名取值, 可以設定 pageNum,pageSize,count,pageSizeZero,reasonable,不設定對映的用預設值, 預設值為pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。--> </plugin> <plugin interceptor="com.a.b.common.sql.SQLInterceptor"/> </plugins>
注意點!!!
pageHelper的依賴jar一定要使用pageHelper原生的jar包 pagehelper-spring-boot-starter jar包 是和spring整合的 PageInterceptor會由spring進行管理 在mybatis載入完後就載入了PageInterceptor 會導致mybatis-config.xml 裡調整攔截器順序失效
錯誤依賴:
<dependency>--> <!--<groupId>com.github.pagehelper</groupId>--> <!--<artifactId>pagehelper-spring-boot-starter</artifactId>--> <!--<version>1.2.12</version>--> </dependency>
正確依賴
<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.1.10</version> </dependency>
@Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}) })
或者
@Intercepts({ @Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), @Signature(type = StatementHandler.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,ResultHandler.class}) })
以上就是解決mybatis分頁外掛PageHelper導致自定義攔截器失效的詳細內容,更多關於mybatis PageHelper攔截器失效的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45