欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解決mybatis分頁(yè)插件PageHelper導(dǎo)致自定義攔截器失效

 更新時(shí)間:2022年08月17日 15:46:39   作者:吃葡萄不吐葡糖皮  
這篇文章主要為大家介紹了解決mybatis分頁(yè)插件PageHelper導(dǎo)致自定義攔截器失效方案示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

問(wèn)題背景

在最近的項(xiàng)目開(kāi)發(fā)中遇到一個(gè)需求 需要對(duì)mysql做一些慢查詢(xún)、大結(jié)果集等異常指標(biāo)進(jìn)行收集監(jiān)控,從運(yùn)維角度并沒(méi)有對(duì)mysql進(jìn)行統(tǒng)一的指標(biāo)搜集,所以需要通過(guò)代碼層面對(duì)指標(biāo)進(jìn)行收集,我采用的方法是通過(guò)mybatis的Interceptor攔截器進(jìn)行指標(biāo)收集在開(kāi)發(fā)中出現(xiàn)了自定義攔截器 對(duì)于查詢(xún)無(wú)法進(jìn)行攔截的問(wèn)題幾經(jīng)周折后終于解決,故進(jìn)行記錄學(xué)習(xí),分享給大家下次遇到少走一些彎路;

mybatis攔截器使用

像springmvc一樣,mybatis也提供了攔截器實(shí)現(xiàn),對(duì)Executor、StatementHandler、ResultSetHandler、ParameterHandler提供了攔截器功能。

使用方法:

在使用時(shí)我們只需要 implements org.apache.ibatis.plugin.Interceptor類(lèi)實(shí)現(xiàn) 方法頭標(biāo)注相應(yīng)注解即可 如下代碼會(huì)對(duì)CRUD的操作進(jìn)行攔截:

@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})
})

注解參數(shù)介紹:

  • @Intercepts:標(biāo)識(shí)該類(lèi)是一個(gè)攔截器;
  • @Signature:指明自定義攔截器需要攔截哪一個(gè)類(lèi)型,哪一個(gè)方法
攔截的類(lèi)(type)攔截的方法(method)
Executorupdate, query, flushStatements, commit, rollback,getTransaction, close, isClosed
ParameterHandlergetParameterObject, setParameters
StatementHandlerprepare, parameterize, batch, update, query
ResultSetHandlerhandleResultSets, handleOutputParameters
  • Executor:提供了增刪改查的接口 攔截執(zhí)行器的方法.
  • StatementHandler:負(fù)責(zé)處理Mybatis與JDBC之間Statement的交互 攔截參數(shù)的處理.
  • ResultSetHandler:負(fù)責(zé)處理Statement執(zhí)行后產(chǎn)生的結(jié)果集,生成結(jié)果列表 攔截結(jié)果集的處理.
  • ParameterHandler:是Mybatis實(shí)現(xiàn)Sql入?yún)⒃O(shè)置的對(duì)象 攔截Sql語(yǔ)法構(gòu)建的處理。

官方代碼示例:

@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(); //被代理對(duì)象
     Method method = invocation.getMethod(); //代理方法
     Object[] args = invocation.getArgs(); //方法參數(shù)
     // do something ...... 方法攔截前執(zhí)行代碼塊
     Object result = invocation.proceed();
     // do something .......方法攔截后執(zhí)行代碼塊
     return result;
   }
   public Object plugin(Object target) {
     return Plugin.wrap(target, this);
   }
}

setProperties方法

因?yàn)閙ybatis框架本身就是一個(gè)可以獨(dú)立使用的框架,沒(méi)有像Spring這種做了很多的依賴(lài)注入。 如果我們的攔截器需要一些變量對(duì)象,而且這個(gè)對(duì)象是支持可配置的。

類(lèi)似于Spring中的@Value("${}")從application.properties文件中獲取。

使用方法:

mybatis-config.xml配置:

<plugin interceptor="com.plugin.mybatis.MyInterceptor">
     <property name="username" value="xxx"/>
     <property name="password" value="xxx"/>
</plugin>

方法中獲取參數(shù):properties.getProperty("username");

bug內(nèi)容:

update類(lèi)型操作可以正常攔截 query類(lèi)型查詢(xún)sql無(wú)法進(jìn)入自定義攔截器,導(dǎo)致攔截失敗以下為部分源碼 由于涉及到公司代碼以下代碼做了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執(zhí)行器4參數(shù)query方法和update類(lèi)型方法 由于mybatis的攔截器為責(zé)任鏈模式調(diào)用有一個(gè)傳遞機(jī)制 (第一個(gè)攔截器執(zhí)行完向下一個(gè)攔截器傳遞 具體實(shí)現(xiàn)可以看一下源碼)

update的操作執(zhí)行確實(shí)進(jìn)了自定義攔截器但是查詢(xún)的操作始終進(jìn)不來(lái)后通過(guò)追蹤源碼發(fā)現(xiàn)

pagehelper插件的 PageInterceptor 攔截器 會(huì)對(duì) Executor執(zhí)行器method=query 的4參數(shù)方法進(jìn)行修改轉(zhuǎn)化為 6參數(shù)方法 向下傳遞 導(dǎo)致執(zhí)行順序在pagehelper后面的攔截器的Executor執(zhí)行器4參數(shù)query方法不會(huì)接收到傳遞過(guò)來(lái)的請(qǐng)求導(dǎo)致攔截器失效

PageInterceptor源碼:

/**
 * Mybatis - 通用分頁(yè)攔截器
 * <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;
            //由于邏輯關(guān)系,只會(huì)進(jìn)入一次
            if (args.length == 4) {
                //4 個(gè)參數(shù)時(shí)
                boundSql = ms.getBoundSql(parameter);
                cacheKey = executor.createCacheKey(ms, parameter, rowBounds, boundSql);
            } else {
                //6 個(gè)參數(shù)時(shí)
                cacheKey = (CacheKey) args[4];
                boundSql = (BoundSql) args[5];
            }
            checkDialectExists();
            List resultList;
            //調(diào)用方法判斷是否需要進(jìn)行分頁(yè),如果不需要,直接返回結(jié)果
            if (!dialect.skip(ms, parameter, rowBounds)) {
                //判斷是否需要進(jìn)行 count 查詢(xún)
                if (dialect.beforeCount(ms, parameter, rowBounds)) {
                    //查詢(xún)總數(shù)
                    Long count = count(executor, ms, parameter, rowBounds, resultHandler, boundSql);
                    //處理查詢(xún)總數(shù),返回 true 時(shí)繼續(xù)分頁(yè)查詢(xún),false 時(shí)直接返回
                    if (!dialect.afterCount(count, parameter, rowBounds)) {
                        //當(dāng)查詢(xún)總數(shù)為 0 時(shí),直接返回空的結(jié)果
                        return dialect.afterPage(new ArrayList(), parameter, rowBounds);
                    }
                }
                resultList = ExecutorUtil.pageQuery(dialect, executor,
                        ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);
            } else {
                //rowBounds用參數(shù)值,不使用分頁(yè)插件處理時(shí),仍然支持默認(rèn)的內(nèi)存分頁(yè)
                resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
            }
            return dialect.afterPage(resultList, parameter, rowBounds);
        } finally {
            if(dialect != null){
                dialect.afterAll();
            }
        }
    }
    /**
     * Spring bean 方式配置時(shí),如果沒(méi)有配置屬性就不會(huì)執(zhí)行下面的 setProperties 方法,就不會(huì)初始化
     * <p>
     * 因此這里會(huì)出現(xiàn) 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;
        //先判斷是否存在手寫(xiě)的 count 查詢(xún)
        MappedStatement countMs = ExecutorUtil.getExistedMappedStatement(ms.getConfiguration(), countMsId);
        if (countMs != null) {
            count = ExecutorUtil.executeManualCount(executor, countMs, parameter, boundSql, resultHandler);
        } else {
            countMs = msCountMap.get(countMsId);
            //自動(dòng)創(chuàng)建
            if (countMs == null) {
                //根據(jù)當(dāng)前的 ms 創(chuàng)建一個(gè)返回值為 Long 類(lèi)型的 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;
        }
    }
}
}

解決方法:

通過(guò)上述我們定位到了問(wèn)題產(chǎn)生的原因 解決起來(lái)就簡(jiǎn)單多了 有倆個(gè)方案如下:

  • 調(diào)整攔截器順序 讓自定義攔截器先執(zhí)行
  • 自定義攔截器query方法也定義為 6參數(shù)方法或者不使用Executor.class執(zhí)行器使用StatementHandler.class執(zhí)行器也可以實(shí)現(xiàn)攔截

解決方案一 調(diào)整執(zhí)行順序

mybatis-config.xml 代碼

我們的自定義攔截器配置的執(zhí)行順序是在PageInterceptor這個(gè)攔截器前面的(先配置后執(zhí)行)

<plugins>
    <!-- com.github.pagehelper為PageHelper類(lèi)所在包名 -->
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- 使用下面的方式配置參數(shù),后面會(huì)有所有的參數(shù)介紹 -->
        <!-- reasonable:分頁(yè)合理化參數(shù),默認(rèn)值為false。當(dāng)該參數(shù)設(shè)置為 true 時(shí),pageNum<=0 時(shí)會(huì)查詢(xún)第一頁(yè), pageNum>pages(超過(guò)總數(shù)時(shí)),會(huì)查詢(xún)最后一頁(yè)。默認(rèn)false 時(shí),直接根據(jù)參數(shù)進(jìn)行查詢(xún)。-->
        <property name="reasonable" value="true"/>
        <!-- supportMethodsArguments:支持通過(guò) Mapper 接口參數(shù)來(lái)傳遞分頁(yè)參數(shù),默認(rèn)值false,分頁(yè)插件會(huì)從查詢(xún)方法的參數(shù)值中,自動(dòng)根據(jù)上面 params 配置的字段中取值,查找到合適的值時(shí)就會(huì)自動(dòng)分頁(yè)。 使用方法可以參考測(cè)試代碼中的 com.github.pagehelper.test.basic 包下的 ArgumentsMapTest 和 ArgumentsObjTest。-->
        <property name="supportMethodsArguments" value="true"/>
        <!-- autoRuntimeDialect:默認(rèn)值為 false。設(shè)置為 true 時(shí),允許在運(yùn)行時(shí)根據(jù)多數(shù)據(jù)源自動(dòng)識(shí)別對(duì)應(yīng)方言的分頁(yè) (不支持自動(dòng)選擇sqlserver2012,只能使用sqlserver),用法和注意事項(xiàng)參考下面的場(chǎng)景五-->
        <property name="autoRuntimeDialect" value="true"/>
        <!-- params:為了支持startPage(Object params)方法,增加了該參數(shù)來(lái)配置參數(shù)映射,用于從對(duì)象中根據(jù)屬性名取值, 可以配置 pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默認(rèn)值, 默認(rèn)值為pageNum=pageNum;pageSize=pageSize;count=countSql;reasonable=reasonable;pageSizeZero=pageSizeZero。-->
    </plugin>
    <plugin interceptor="com.a.b.common.sql.SQLInterceptor"/>
</plugins>

注意點(diǎn)?。。?/p>

pageHelper的依賴(lài)jar一定要使用pageHelper原生的jar包 pagehelper-spring-boot-starter jar包 是和spring集成的 PageInterceptor會(huì)由spring進(jìn)行管理 在mybatis加載完后就加載了PageInterceptor 會(huì)導(dǎo)致mybatis-config.xml 里調(diào)整攔截器順序失效

錯(cuò)誤依賴(lài):

<dependency>-->
    <!--<groupId>com.github.pagehelper</groupId>-->
    <!--<artifactId>pagehelper-spring-boot-starter</artifactId>-->
    <!--<version>1.2.12</version>-->
</dependency>

正確依賴(lài)

<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分頁(yè)插件PageHelper導(dǎo)致自定義攔截器失效的詳細(xì)內(nèi)容,更多關(guān)于mybatis PageHelper攔截器失效的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • java中BCryptPasswordEncoder密碼的加密與驗(yàn)證方式

    java中BCryptPasswordEncoder密碼的加密與驗(yàn)證方式

    這篇文章主要介紹了java中BCryptPasswordEncoder密碼的加密與驗(yàn)證方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-08-08
  • SpringBoot通過(guò)注解注入Bean的幾種方式解析

    SpringBoot通過(guò)注解注入Bean的幾種方式解析

    這篇文章主要為大家介紹了SpringBoot注入Bean的幾種方式示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步早日升職加薪
    2022-03-03
  • Java基于jeeplus vue實(shí)現(xiàn)簡(jiǎn)單工作流過(guò)程圖解

    Java基于jeeplus vue實(shí)現(xiàn)簡(jiǎn)單工作流過(guò)程圖解

    這篇文章主要介紹了Java基于jeeplus vue實(shí)現(xiàn)簡(jiǎn)單工作流過(guò)程圖解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 如何動(dòng)態(tài)替換Spring容器中的Bean

    如何動(dòng)態(tài)替換Spring容器中的Bean

    這篇文章主要介紹了如何動(dòng)態(tài)替換Spring容器中的Bean,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • Springboot+Redis實(shí)現(xiàn)API接口限流的示例代碼

    Springboot+Redis實(shí)現(xiàn)API接口限流的示例代碼

    本文主要介紹了Springboot+Redis實(shí)現(xiàn)API接口限流的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-07-07
  • JavaWeb中web.xml初始化加載順序詳解

    JavaWeb中web.xml初始化加載順序詳解

    本篇文章主要介紹了JavaWeb中web.xml初始化加載順序詳解,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-05-05
  • SpringBoot登錄攔截配置詳解(實(shí)測(cè)可用)

    SpringBoot登錄攔截配置詳解(實(shí)測(cè)可用)

    這篇文章主要介紹了SpringBoot登錄攔截配置詳解(實(shí)測(cè)可用),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • 如何將Spring Session存儲(chǔ)到Redis中實(shí)現(xiàn)持久化

    如何將Spring Session存儲(chǔ)到Redis中實(shí)現(xiàn)持久化

    這篇文章主要介紹了如何將Spring Session存儲(chǔ)到Redis中實(shí)現(xiàn)持久化,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-07-07
  • Spring中基于xml的聲明式事務(wù)示例詳解

    Spring中基于xml的聲明式事務(wù)示例詳解

    這篇文章主要介紹了Spring之基于xml的聲明式事務(wù),本文通過(guò)圖文實(shí)例代碼相結(jié)合給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2023-09-09
  • 新手學(xué)習(xí)Java對(duì)Redis簡(jiǎn)單操作

    新手學(xué)習(xí)Java對(duì)Redis簡(jiǎn)單操作

    這篇文章主要介紹了新手學(xué)習(xí)Java對(duì)Redis簡(jiǎn)單操作,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04

最新評(píng)論