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

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ī)制之后,理解起來就相對容易一些。

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

我們不需要一次性對四種類型的攔截器都了解,因?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對象,在mapper.xml中寫一個(gè)簡單的獲取數(shù)據(jù)的sql、使用mapper對象通過該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)對象的一部分,回調(diào)對象其實(shí)是Plugin,Plugin對象持有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è)包裝對象,包含了被代理對象(原對象)、被代理的方法、以及方法調(diào)用參數(shù)等。
  • 通過Invocation.proceed()執(zhí)行被代理對象的原方法,所以在該方法前、后可以添加我們自己的增強(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)然,我們也可以對多個(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è)攔截器是怎么生效的。

攔截器的初始化

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

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

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

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

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對象以參數(shù)的形式傳遞過去:

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

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

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

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

攔截器是如何生效的#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)代理的方式,返回該對象的一個(gè)代理對象,回調(diào)對象為持有原對象、攔截器、攔截方法簽名的Plugin對象。

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

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

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

上面一節(jié)分析了openSession過程中,Executor代理對象是如何被創(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對象的所有注冊的攔截方法,比較當(dāng)前調(diào)用的方法是否為攔截方法,是的話就調(diào)用攔截器的intercept方法......就是我們自己編寫的攔截器的攔截方法。否則如果當(dāng)前方法沒有配置攔截的話就調(diào)用原方法。

調(diào)用攔截器的攔截方法的時(shí)候,創(chuàng)建了一個(gè)持有被代理對象target、攔截方法、攔截方法的調(diào)用參數(shù)...等數(shù)據(jù)的Invocation對象作為參數(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)文章

  • 使用SpringBoot整合Jpa的過程詳解

    使用SpringBoot整合Jpa的過程詳解

    SpringBoot是一種快速開發(fā)框架,它簡化了Java應(yīng)用程序的開發(fā)過程,而Jpa是Java持久化規(guī)范的一種實(shí)現(xiàn),將SpringBoot與Jpa整合可以更加方便地進(jìn)行數(shù)據(jù)庫操作,提高開發(fā)效率,本文將介紹如何使用Spring Boot整合Jpa,幫助讀者快速上手并應(yīng)用于實(shí)際項(xiàng)目中
    2023-12-12
  • 實(shí)例解析Java關(guān)于static的作用

    實(shí)例解析Java關(guān)于static的作用

    只要是有學(xué)過Java的都一定知道static,也一定能多多少少說出一些作用和注意事項(xiàng)。文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • Java?HashMap中除了死循環(huán)之外的那些問題

    Java?HashMap中除了死循環(huán)之外的那些問題

    這篇文章主要介紹了Java?HashMap中除了死循環(huán)之外的那些問題,這些問題大致可以分為兩類,程序問題和業(yè)務(wù)問題,下面文章我們一個(gè)一個(gè)來看,需要的小伙伴可以參考一下
    2022-05-05
  • 詳解Spring AOP自定義可重復(fù)注解沒有生效問題

    詳解Spring AOP自定義可重復(fù)注解沒有生效問題

    本文主要介紹了Spring AOP自定義可重復(fù)注解沒有生效問題,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • 2018版java多線程面試題集合及答案

    2018版java多線程面試題集合及答案

    這篇文章主要為大家詳細(xì)介紹了2018版java多線程面試題集合及答案,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-09-09
  • Java中StringBuilder字符串類型的操作方法及API整理

    Java中StringBuilder字符串類型的操作方法及API整理

    Java中的StringBuffer類繼承于AbstractStringBuilder,用來創(chuàng)建非線程安全的字符串類型對象,下面即是對Java中StringBuilder字符串類型的操作方法及API整理
    2016-05-05
  • 深入理解java重載和重寫

    深入理解java重載和重寫

    這篇文章主要介紹了Java方法重載和重寫原理區(qū)別解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2021-07-07
  • Java開發(fā)中常用記錄

    Java開發(fā)中常用記錄

    這篇文章主要介紹了Java-編程式事務(wù)、Java-Stream、Linux常用命令,需要的朋友可以參考下
    2023-05-05
  • java控制臺(tái)實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(集合版)

    java控制臺(tái)實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)(集合版)

    這篇文章主要為大家詳細(xì)介紹了java控制臺(tái)實(shí)現(xiàn)學(xué)生信息管理系統(tǒng)的集合版,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-04-04
  • Jmeter生成UUID作為唯一標(biāo)識(shí)符過程圖解

    Jmeter生成UUID作為唯一標(biāo)識(shí)符過程圖解

    這篇文章主要介紹了Jmeter生成UUID作為唯一標(biāo)識(shí)符過程圖解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-08-08

最新評論