最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

mybatis攔截器注冊初始化編寫示例及如何生效詳解

 更新時(shí)間:2023年08月29日 14:14:29   作者:福  
這篇文章主要為大家介紹了mybatis攔截器注冊初始化編寫示例及如何生效詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

Mybatis支持四種類型的攔截器

這一點(diǎn)可以從Mybatis的初始化類Configuration.java中得到驗(yàn)證(源碼體不貼出了,改天分析Mybatis初始化過程的時(shí)候詳細(xì)說)。具體包括:

  • ParameterHandler攔截器
  • ResultSetHandler攔截器
  • StatementHandler攔截器
  • Executor攔截器

四種攔截器分別有各自不同的用途,當(dāng)我們熟悉Mybatis的運(yùn)行機(jī)制之后,理解起來就相對(duì)容易一些。

目前,如果我們對(duì)Mybatis還不是很了解的話,也沒有關(guān)系,不影響我們對(duì)Mybatis的攔截器做初步的了解。

我們不需要一次性對(duì)四種類型的攔截器都了解,因?yàn)樗麄兊墓ぷ鳈C(jī)制及底層原理大致相同。

我們今天以Executor攔截器為切入點(diǎn),了解Mybatis攔截器的實(shí)現(xiàn)方法、以及初步分析其實(shí)現(xiàn)原理。

今天的目標(biāo)是:用Mybatis攔截器技術(shù),計(jì)算每一句sql語句的執(zhí)行時(shí)長,并在控制臺(tái)打印出來具體的sql語句及參數(shù)。

在此過程中,我們會(huì)了解:

  • 編寫Mybatis攔截器。
  • Mybatis攔截器注冊。
  • Mybatis攔截器的初始化過程。
  • Mybatis攔截器是如何生效的。

準(zhǔn)備工作

Springboot項(xiàng)目,并引入Mybatis,pom文件加入依賴:

<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
     <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>2.1.3</version>
</dependency>

然后配置數(shù)據(jù)庫訪問、建表、創(chuàng)建mapper.xml文件及mapper對(duì)象,在mapper.xml中寫一個(gè)簡單的獲取數(shù)據(jù)的sql、使用mapper對(duì)象通過該sql語句獲取數(shù)據(jù)。

今天文章的主要目標(biāo)是攔截器,所以以上關(guān)于通過Mybatis獲取數(shù)據(jù)庫數(shù)據(jù)的代碼就不貼出了。

編寫攔截器

Mybatis攔截器是AOP的一個(gè)具體實(shí)現(xiàn),我們前面文章分析過AOP的實(shí)現(xiàn)原理其實(shí)就是動(dòng)態(tài)代理,java實(shí)現(xiàn)動(dòng)態(tài)代理有兩種方式:cglib和java原生(我們前面有一篇文章專門分析過兩者的區(qū)別),Mybatis攔截器是通過java原生的方式實(shí)現(xiàn)的。

其實(shí)我們實(shí)現(xiàn)的攔截器在java原生動(dòng)態(tài)代理的框架中屬于回調(diào)對(duì)象的一部分,回調(diào)對(duì)象其實(shí)是Plugin,Plugin對(duì)象持有Interceptor,Plugin的invoke方法才是JDK動(dòng)態(tài)代理中的那個(gè)回調(diào)方法、其中會(huì)調(diào)用Interceptor的intercept方法,所以Plugin的invoke方法其實(shí)又類似于一個(gè)模板方法(這部分后面會(huì)有具體分析)。

所以Mybatis都已經(jīng)替我們安排好了,我們的攔截器只需要實(shí)現(xiàn)這個(gè)intercept方法即可。

@Slf4j
@Component
@Intercepts(@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, RowBounds.class,
        ResultHandler.class}))
public class myInterceptor implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement ms = (MappedStatement) invocation.getArgs()[0];
        Object param = invocation.getArgs()[1];
        BoundSql boundSql = ms.getBoundSql(param);
        String sql=boundSql.getSql();
        sql=sql.trim().replaceAll("\\s+", " ");
        log.info("sql:"+ sql);
        log.info("param:" + param);
        long startTime=System.currentTimeMillis();
        Object result=invocation.proceed();
        long endTime=System.currentTimeMillis();
        log.info("sql statement take :"+ (endTime - startTime));
        return result;
    }
}

要實(shí)現(xiàn)的目標(biāo)都在上面這段代碼中,一目了然。

需要解釋以下幾點(diǎn):

  • @Intercepts注解:目的是為了告訴Mybatis當(dāng)前攔截器的類型(開篇說的四種類型之一)、攔截方法名以及方法參數(shù)。
  • Invocation:攔截器被調(diào)用的時(shí)候組裝起來的一個(gè)包裝對(duì)象,包含了被代理對(duì)象(原對(duì)象)、被代理的方法、以及方法調(diào)用參數(shù)等。
  • 通過Invocation.proceed()執(zhí)行被代理對(duì)象的原方法,所以在該方法前、后可以添加我們自己的增強(qiáng)功能,比如計(jì)算sql語句執(zhí)行時(shí)長就是在方法執(zhí)行前、后分別獲取系統(tǒng)時(shí)間并計(jì)算時(shí)間差即可。
  • Executor有兩個(gè)query方法,我們需要清楚地知道應(yīng)用最終會(huì)調(diào)用Executor的哪個(gè)query方法,否則如果匹配不上的話就不會(huì)執(zhí)行攔截。當(dāng)然,我們也可以對(duì)多個(gè)方法執(zhí)行攔截。
  • invocation.getArgs()[0]獲取到的是被代理方法的第一個(gè)參數(shù),以此類推......可以獲取到被代理方法的所有參數(shù),所以在攔截器中可以有完整的被代理方法的執(zhí)行現(xiàn)場,能做到一個(gè)攔截器理論上能做的任何事情。

好了,攔截器代碼我們就完成了。

攔截器的注冊

攔截器編寫完成后,需要注冊到Mybatis的InterceptorChain中才能生效。

我們可以看到Mybatis的攔截器又是一個(gè)chain的概念,所以我們是可以實(shí)現(xiàn)多個(gè)攔截器,每一個(gè)攔截器各自實(shí)現(xiàn)自己的目標(biāo)的。

可以通過以下幾種方式實(shí)現(xiàn)攔截器的注冊:

  • 在mybatis.xml文件中通過plugins標(biāo)簽配置
  • 通過配置類,創(chuàng)建ConfigurationCustomizer類實(shí)現(xiàn)customize方法
  • Spring項(xiàng)目中將攔截器注冊到Spring Ioc容器中

我們當(dāng)前是基于Springboot的項(xiàng)目,所以上面代碼中已經(jīng)加了@Component注解,通過第3種方式完成注冊,簡單方便。

運(yùn)行

攔截器準(zhǔn)備好了,啟動(dòng)項(xiàng)目,隨便跑一個(gè)數(shù)據(jù)查詢的方法:

可以看到攔截器已經(jīng)可以正常工作了。

上面我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡單的Executor攔截器,下面我們要花點(diǎn)時(shí)間分析一下這個(gè)攔截器是怎么生效的。

攔截器的初始化

在尚未對(duì)Mybatis的初始化過程進(jìn)行整體分析的情況下,想要徹底搞清楚攔截器的初始化過程多少有點(diǎn)困難,但是如果我們只看Mybatis初始化過程中與攔截器有關(guān)的部分的話,也不是不可以。

Mybatis初始化的過程中會(huì)通過SqlSessionFatoryBuilder創(chuàng)建SqlSessionFactory,SqlSessionFactory會(huì)持有Configuration對(duì)象。

而我們前面所說的注冊Mybatis攔截器,不論以什么樣的方式進(jìn)行注冊,其目的無非就是要讓Mybatis啟動(dòng)、初始化的過程中,將攔截器注冊到Configuration對(duì)象中。

比如我們上面所說的任何一種注冊方式,最終SqlSessionFactoryBean都會(huì)將攔截器獲取到plugins屬性中,在buildSqlSessionFactory()方法中將攔截器注冊到Configuration對(duì)象中:

if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }
// 省略代碼
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);

最后調(diào)用SqlSessionFactoryBuilder的build方法創(chuàng)建SqlSessionFactory,我們從源碼可以看到最終創(chuàng)建了DefaultSqlSessionFactory,并且將Configuration對(duì)象以參數(shù)的形式傳遞過去:

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

而DefaultSqlSessionFactory會(huì)持有該Configuration對(duì)象:

public DefaultSqlSessionFactory(Configuration configuration) {
    this.configuration = configuration;
  }

所以,Mybatis初始化的過程中會(huì)獲取到我們注冊的攔截器,該攔截器會(huì)注冊到Configuration對(duì)象中,最終,SqlSesscionFactory對(duì)象會(huì)持有Configuration對(duì)象,從而持有該攔截器。

攔截器是如何生效的#openSession

那我們現(xiàn)在看一下,已經(jīng)完成初始化的攔截器最終是如何生效的。

我們知道一條數(shù)據(jù)庫操作語句的執(zhí)行首先是要調(diào)用SqlSesscionFactory的openSession來獲取sqlSession開始的。

上面我們已經(jīng)看到初始化過程中創(chuàng)建的是DefaultSqlSessionFactory,所以我們直接看DefaultSqlSessionFactory的openSession方法。

最終會(huì)調(diào)用到openSessionFromDataSource或openSessionFromConnection,兩個(gè)方法的結(jié)構(gòu)差不太多,但是具體細(xì)節(jié)的區(qū)分今天就不做分析了。我們直接看openSessionFromDataSource:

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

關(guān)注的重點(diǎn)放在final Executor executor = configuration.newExecutor(tx, execType)上,我們?nèi)タ匆幌翪onfiguraton的這個(gè)方法:

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      executor = new ReuseExecutor(this, transaction);
    } else {
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

方法最后階段獲取到Excutor后,調(diào)用interceptorChain.pluginAll,該方法逐個(gè)調(diào)用攔截器的plugin方法,攔截器的plugin方法調(diào)用Plugin的wrap方法:

public static Object wrap(Object target, Interceptor interceptor) {
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    return target;
  }

最終通過動(dòng)態(tài)代理的方式,返回該對(duì)象的一個(gè)代理對(duì)象,回調(diào)對(duì)象為持有原對(duì)象、攔截器、攔截方法簽名的Plugin對(duì)象。

所以我們知道,openSession最終創(chuàng)建的DefaultSqlSession所持有的Executor其實(shí)是已經(jīng)被攔截器處理過的代理對(duì)象。

根據(jù)我們對(duì)JDK代理的理解,最終Executor的方法被調(diào)用的時(shí)候,其實(shí)是要回調(diào)這個(gè)代理對(duì)象創(chuàng)建的時(shí)候的回調(diào)器的invoke方法的,也就是Plugin的invoke方法。

攔截器是如何生效的#Executor執(zhí)行

上面一節(jié)分析了openSession過程中,Executor代理對(duì)象是如何被創(chuàng)建的。

接下來看一下具體的Executor的執(zhí)行,本例攔截的是他的query方法。其實(shí)我們已經(jīng)知道query方法執(zhí)行的時(shí)候是要調(diào)用Plugin的invoke方法的。

代碼其實(shí)比較簡單:

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

獲取到當(dāng)前Executor對(duì)象的所有注冊的攔截方法,比較當(dāng)前調(diào)用的方法是否為攔截方法,是的話就調(diào)用攔截器的intercept方法......就是我們自己編寫的攔截器的攔截方法。否則如果當(dāng)前方法沒有配置攔截的話就調(diào)用原方法。

調(diào)用攔截器的攔截方法的時(shí)候,創(chuàng)建了一個(gè)持有被代理對(duì)象target、攔截方法、攔截方法的調(diào)用參數(shù)...等數(shù)據(jù)的Invocation對(duì)象作為參數(shù)傳進(jìn)去。這也就是為什么我們在攔截器方法中能獲取到這些數(shù)據(jù)的原因。

OK...還差一點(diǎn),就是如果配置了多個(gè)代理器的話,調(diào)用順序的問題。其實(shí)整體比較起來,Mybatis的源碼感覺比Spring的簡單了許多,攔截器注冊之后在InterceptorChain也就是保存在ArrayList中,所以他本身應(yīng)該是沒有順序的,想要控制調(diào)用順序應(yīng)該還得想其他辦法。

以上就是mybatis攔截器注冊初始化編寫示例及如何生效詳解的詳細(xì)內(nèi)容,更多關(guān)于mybatis攔截器的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java RMI詳細(xì)介紹及簡單實(shí)例

    Java RMI詳細(xì)介紹及簡單實(shí)例

    這篇文章主要介紹了Java RMI詳細(xì)介紹及簡單實(shí)例的相關(guān)資料,需要的朋友可以參考下
    2017-02-02
  • Java中的switch-case語句的語法、特征詳解

    Java中的switch-case語句的語法、特征詳解

    Java中的switch-case語句用于多分支選擇,支持多種數(shù)據(jù)類型,包括整型、字符型、枚舉型、字符串等,它提供了一種更清晰、更高效的方式來處理多個(gè)條件分支,本文給大家介紹Java中的switch-case語句的語法、特征詳解,感興趣的朋友一起看看吧
    2025-11-11
  • 線上Spring CPU 高負(fù)載解決思路詳解

    線上Spring CPU 高負(fù)載解決思路詳解

    這篇文章主要為大家介紹了線上Spring CPU 高負(fù)載解決思路詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-09-09
  • Spring Boot中整合Spring Security并自定義驗(yàn)證代碼實(shí)例

    Spring Boot中整合Spring Security并自定義驗(yàn)證代碼實(shí)例

    本篇文章主要介紹了Spring Boot中整合Spring Security并自定義驗(yàn)證代碼實(shí)例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-04-04
  • 如何修改logback.xml配置文件在resource以外的位置

    如何修改logback.xml配置文件在resource以外的位置

    這篇文章主要介紹了如何修改logback.xml配置文件在resource以外的位置,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2021-02-02
  • 如何基于springboot-admin實(shí)現(xiàn)后臺(tái)監(jiān)控

    如何基于springboot-admin實(shí)現(xiàn)后臺(tái)監(jiān)控

    這篇文章主要介紹了如何基于springboot-admin實(shí)現(xiàn)后臺(tái)監(jiān)控,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-04-04
  • 使用abstract格式修飾抽象方法

    使用abstract格式修飾抽象方法

    abstract是抽象的意思,用于修飾方法方法和類,修飾的方法是抽象方法,修飾的類是抽象類,這篇文章主要介紹了怎樣使用abstract格式修飾抽象方法,需要的朋友可以參考下
    2023-05-05
  • 基于mybatis進(jìn)行批量更新兩種方法

    基于mybatis進(jìn)行批量更新兩種方法

    這篇文章主要給大家介紹了關(guān)于如何基于mybatis進(jìn)行批量更新的兩種方法,批量更新的使用,mybatis中批量更新有很多種方法,可以把數(shù)據(jù)一條條更新,也可以傳入一個(gè)數(shù)據(jù)集一次性更新,需要的朋友可以參考下
    2023-08-08
  • Java中的ArrayList(擴(kuò)容機(jī)制)詳解

    Java中的ArrayList(擴(kuò)容機(jī)制)詳解

    ArrayList作為Java中廣泛使用的動(dòng)態(tài)數(shù)組,其擴(kuò)容機(jī)制是保證性能和內(nèi)存使用平衡的關(guān)鍵,默認(rèn)初始容量為10,擴(kuò)容因子為1.5,旨在減少頻繁的內(nèi)存分配和數(shù)據(jù)遷移代價(jià),同時(shí)建議使用預(yù)估計(jì)的初始化容量以減少擴(kuò)容次數(shù)
    2024-11-11
  • java中for循環(huán)刪除集合陷阱

    java中for循環(huán)刪除集合陷阱

    java中在增強(qiáng)for循環(huán)為什么不能增刪集合呢?一個(gè)循環(huán)迭代,跟集合的增刪改沒什么關(guān)系。修改集合不是for去做的。for只管循環(huán)迭代,你在循環(huán)里邊修改集合,改變集合的長度,順序?qū)ρh(huán)都有影響
    2015-05-05

最新評(píng)論

兴文县| 蛟河市| 阳曲县| 溧阳市| 双流县| 敦煌市| 大竹县| 台东市| 仁寿县| 崇文区| 利辛县| 乌恰县| 石楼县| 龙州县| 弥渡县| 宁阳县| 新竹县| 天长市| 崇左市| 古浪县| 余姚市| 惠来县| 榆林市| 侯马市| 东明县| 萨嘎县| 那坡县| 庆城县| 遂宁市| 武威市| 桂阳县| 宣汉县| 卢湾区| 哈巴河县| 新巴尔虎左旗| 繁峙县| 乐平市| 余江县| 加查县| 无锡市| 南溪县|