亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

Mybatis支持四種類型的攔截器

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

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

四種攔截器分別有各自不同的用途,當我們熟悉Mybatis的運行機制之后,理解起來就相對容易一些。

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

我們不需要一次性對四種類型的攔截器都了解,因為他們的工作機制及底層原理大致相同。

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

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

在此過程中,我們會了解:

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

準備工作

Springboot項目,并引入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中寫一個簡單的獲取數(shù)據(jù)的sql、使用mapper對象通過該sql語句獲取數(shù)據(jù)。

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

編寫攔截器

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

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

所以Mybatis都已經(jīng)替我們安排好了,我們的攔截器只需要實現(xiàn)這個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;
    }
}

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

需要解釋以下幾點:

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

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

攔截器的注冊

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

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

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

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

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

運行

攔截器準備好了,啟動項目,隨便跑一個數(shù)據(jù)查詢的方法:

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

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

攔截器的初始化

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

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

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

比如我們上面所說的任何一種注冊方式,最終SqlSessionFactoryBean都會將攔截器獲取到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會持有該Configuration對象:

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

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

攔截器是如何生效的#openSession

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

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

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

最終會調(diào)用到openSessionFromDataSource或openSessionFromConnection,兩個方法的結(jié)構(gòu)差不太多,但是具體細節(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();
    }
  }

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

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,該方法逐個調(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;
  }

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

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

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

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

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

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

代碼其實比較簡單:

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

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

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

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

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

相關文章

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

    使用SpringBoot整合Jpa的過程詳解

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

    實例解析Java關于static的作用

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

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

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

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

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

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

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

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

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

    深入理解java重載和重寫

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

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

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

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

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

    Jmeter生成UUID作為唯一標識符過程圖解

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

最新評論