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

Mybatis?plugin的使用及原理示例解析

 更新時(shí)間:2023年09月26日 08:51:29   作者:戰(zhàn)斧  
這篇文章主要為大家介紹了?Mybatis?plugin的使用及原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

上次,我們說過了mybatis+springboot時(shí)的啟動(dòng)與執(zhí)行流程,也介紹過mybatis的執(zhí)行器和緩存,今天,我們來看看mybatis 的另一個(gè)大功能 —— plugin

一、Mybatis Plugin 是什么

MyBatis的plugin插件是用來攔截SQL執(zhí)行的,對SQL進(jìn)行增強(qiáng)的一種機(jī)制。

MyBatis的Plugin實(shí)現(xiàn)基于JDK動(dòng)態(tài)代理機(jī)制,在MyBatis初始化過程中,可以為指定的攔截對象生成代理對象,當(dāng)攔截對象執(zhí)行某個(gè)方法時(shí),代理會先執(zhí)行插件中的邏輯,再執(zhí)行原有邏輯。插件可以在原有邏輯前后添加自己的邏輯或者完全替換原有邏輯

如果你使用過spring的話,會自然的想到spring的AOP特性,兩者都是利用代理來實(shí)現(xiàn)功能的增強(qiáng)

二、Mybatis Plugin 的實(shí)例

這是一個(gè)舊項(xiàng)目,在后期對接Oracle后,有很多sql報(bào)了錯(cuò),其原因是使用 instr() 函數(shù)時(shí),由于參數(shù)是外部傳入的,有時(shí)候可能會傳來一個(gè)幾千長度的字符串,從而導(dǎo)致instr 超長報(bào)錯(cuò)。因?yàn)檫@樣的sql還有很多,不可能一一去改,所以必須使用功能增強(qiáng)的方式來解決

在直接上示例之前,我們先看看官方提供的接口 Interceptor.java ,只要實(shí)現(xiàn)了該接口,就可以在指定位置發(fā)揮作用

package org.apache.ibatis.plugin;
public interface Interceptor {
  /**
  * intercept方法就是要進(jìn)行攔截的時(shí)候要執(zhí)行的方法
  */
  Object intercept(Invocation invocation) throws Throwable;
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  default void setProperties(Properties properties) {
    // NOP
  }
}

當(dāng)然,這個(gè)接口還需要配合另一個(gè)注解 @Intercepts 使用,我們結(jié)合案例寫一個(gè)插件看看

@Component
@Intercepts({ @Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = { Connection.class, Integer.class }) })
public class ExamplePlugin implements Interceptor {
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
        BoundSql boundSql = statementHandler.getBoundSql();
        String sql = boundSql.getSql();
        String newSql = null;
        // 改造sql部分省略,主要是將 instr() 拆分成 instr() or instr() 的形式以降低每個(gè)括號內(nèi)	的長度...
        // newsql = sql.replace(...)
        // 把新的sql通過反射重新設(shè)置回去
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql , newSql);
        Object returnVal = invocation.proceed();
        return returnVal;
    }
}

不難看出,配上注解后,該插件的意思就是針對 StatementHandler.prepare(Connection, Integer) 方法進(jìn)行增強(qiáng),我們實(shí)際運(yùn)行下看看:

如圖,最終走到了我們寫的插件的 intercept 方法中

需要注意的是 @Intercepts 注解內(nèi)支持配置 @Signature 數(shù)組,并以逗號分割。也就是說一個(gè)攔截器其實(shí)可以攔截多個(gè)類的方法,如下

@Intercepts({
        @Signature(
                type = ResultSetHandler.class,
                method = "handleResultSets", 
                args = {Statement.class}),
        @Signature(type = Executor.class,
                method = "query",
                args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})
})

三、Mybatis Plugin 原理

1. Mybatis 支持哪些 Plugin

其實(shí)從上面,案例看出,我們對插件的設(shè)置主要是通過 @Intercepts 內(nèi)的 @Signature 注解實(shí)現(xiàn)的

@Intercepts({ @Signature(
        type = StatementHandler.class,
        method = "prepare",
        args = { Connection.class, Integer.class }) })

其中,type 就是作用的接口,method 和 args 則能確定唯一方法(單用方法名,可能有方法重載的情況)
但是,是不是這些屬性可以隨便填呢?其實(shí)不是的,mybatis沒有做的那么自由,其更像Spring中的postProcessor機(jī)制,只在固定的幾個(gè)位置有預(yù)留點(diǎn),讓你可以自定義增強(qiáng),而不是開放所有位置。

這里的Pulgin只針對以下四個(gè)接口有增強(qiáng)預(yù)留點(diǎn),它們分別是

  • Statementhandler
    用于處理JDBC Statement對象的相關(guān)操作,將SQL語句中的占位符進(jìn)行替換,然后使用Statement對象執(zhí)行SQL語句
  • Resultsethandler
    主要負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集轉(zhuǎn)化為Java對象,然后返還給調(diào)用方
  • ParameterHandler
    主要用于處理Java對象與JDBC參數(shù)的映射,并將其轉(zhuǎn)化為JDBC參數(shù)。
  • Executor
    更頂層的設(shè)計(jì),能對上三種類進(jìn)行調(diào)用,執(zhí)行SQL語句,并獲取執(zhí)行結(jié)果

其具體調(diào)用鏈路如下:

2. myBatis 如何加載 Plugin

即我們自己創(chuàng)建了個(gè) Interceptor 實(shí)現(xiàn)類,也使用了 @Intercepts 注解,但這個(gè)類是如何被mybatis加載的呢?

2.1 springboot 項(xiàng)目

我們?nèi)砸陨掀恼碌膕pringboot+mybatis為例,那么此處便又要提到spring-boot的自動(dòng)配置了,我們看下 MybatisAutoConfiguration (mybatis-spring-boot-autoconfigure2.1.4版本)這個(gè)自動(dòng)配置類,看其構(gòu)造方法

@org.springframework.context.annotation.Configuration
@ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties(MybatisProperties.class)
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
public class MybatisAutoConfiguration implements InitializingBean {
  // 省略部分代碼
  public MybatisAutoConfiguration(MybatisProperties properties, ObjectProvider<Interceptor[]> interceptorsProvider,
      ObjectProvider<TypeHandler[]> typeHandlersProvider, ObjectProvider<LanguageDriver[]> languageDriversProvider,
      ResourceLoader resourceLoader, ObjectProvider<DatabaseIdProvider> databaseIdProvider,
      ObjectProvider<List<ConfigurationCustomizer>> configurationCustomizersProvider) {
    this.properties = properties;
    this.interceptors = interceptorsProvider.getIfAvailable();
    this.typeHandlers = typeHandlersProvider.getIfAvailable();
    this.languageDrivers = languageDriversProvider.getIfAvailable();
    this.resourceLoader = resourceLoader;
    this.databaseIdProvider = databaseIdProvider.getIfAvailable();
    this.configurationCustomizers = configurationCustomizersProvider.getIfAvailable();
  }
}

其構(gòu)造方法的第二個(gè)參數(shù) :ObjectProvider<Interceptor[]>

ObjectProvider 是在spring 4.3 引入的一種注入方式,它可以檢索指定的類型。

然后通過 getIfAvailable 和 getIfUnique 從spring容器中檢索出對應(yīng)對象

因?yàn)槲覀円呀?jīng)在自定義的 ExamplePlugin 上使用了@Component 的注解,所以此處使用自動(dòng)注入,能獲取到我們的插件理所當(dāng)然。

而后再把該值賦給 sqlSessionFactoryBean, 然后再賦給 mybatis 真正的配置類 Configuration。至此,我們的插件就被 mybatis 系統(tǒng)所成功加載了。

2.2 spring 項(xiàng)目

如果還沒有使用上spring-boot,沒有所謂的自動(dòng)配置,那也無妨,只是需要手動(dòng)額外配置一點(diǎn)參數(shù)也是同樣的。

如:已經(jīng)在 application.properties 配置了mybatis 配置文件

mybatis.config.location: classpath:/mybatis-config.xml

然后在mybatis-config.xml 里加上如下配置

<configuration>    
	<plugins>
        <plugin interceptor="com.zhanfu.spring.demo.utils.ExamplePlugin"/>
    </plugins>
</configuration>

這樣也能達(dá)到,將指定插件放入 mybatis 框架的效果

3. Plugin 生效原理

上面我們講了,如何寫一個(gè)插件,以及插件是怎么交給 myBatis框架的,現(xiàn)在要談最重要的內(nèi)容了。即myBatis 是如何利用插件的。

上文我們已經(jīng)了解到了,所有的插件實(shí)例都被放入了 myBatis 的總配置類 Configuration 去管理,成為了該類的一個(gè)屬性interceptorChain ,該類詳情如下:

public class InterceptorChain {
  // 所有的插件都存在這個(gè) List 中
  private final List<Interceptor> interceptors = new ArrayList<>();
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

所以我們只要看該類拿這些插件做了什么即可,可以看到,該類對所有新建的目標(biāo)對象,都進(jìn)行了 pluginAll 操作,結(jié)合上圖,我們不難看出,該方法其實(shí)就是遍歷所有插件,然后調(diào)用每個(gè)插件的 plugin() 方法 生成一個(gè)新對象,然后下一個(gè)插件拿這個(gè)新對象再 plugin() 生成一個(gè)新對象,實(shí)際上構(gòu)成了一套鏈?zhǔn)降那短?/p>

那么plugin() 方法到底做了什么呢?我們來看看回頭再來看看 Interceptor 接口里,該方法的默認(rèn)實(shí)現(xiàn)

  // Interceptor.java
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  // Plugin.java
  public static Object wrap(Object target, Interceptor interceptor) {
    // 從插件的注解中,解析出該插件可作用的接口,以及該類下的哪些方法
    Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
    Class<?> type = target.getClass();
    // 找到插件可作用的接口和目標(biāo)類的中所有重合的接口
    Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
    // 如果有重合的接口,則生成jdk代理并返回。注意,interceptor是我們的插件對象,signatureMap是插件注釋解析到的類與方法
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    // 如果沒有,則返回原對象
    return target;
  }

綜上,不難看出,只有生成指定的四種實(shí)例時(shí),才會進(jìn)入上述代碼生成代理,最后返還的其實(shí)就是代理對象。需要注意的是,此時(shí)的代理是能夠代理這些接口的所有方法的,要想實(shí)現(xiàn)指定方法才使用代理,還得依靠代理的 invoke 方法內(nèi)去篩選

  // Plugin.java
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      // 只有注解上指定方法才能走插件對象的 intercept 方法
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        return interceptor.intercept(new Invocation(target, method, args));
      }
      // 其他方法盡管經(jīng)過代理,但其實(shí)什么也沒做,直接調(diào)用原對象去了
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

以上就是 Mybatis plugin 的使用及原理解析的詳細(xì)內(nèi)容,更多關(guān)于 Mybatis plugin使用原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 簡單的一次springMVC路由跳轉(zhuǎn)實(shí)現(xiàn)

    簡單的一次springMVC路由跳轉(zhuǎn)實(shí)現(xiàn)

    本文主要介紹了springMVC路由跳轉(zhuǎn)實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • java關(guān)于list集合做刪除操作時(shí)的坑及解決

    java關(guān)于list集合做刪除操作時(shí)的坑及解決

    這篇文章主要介紹了java關(guān)于list集合做刪除操作時(shí)的坑及解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-11-11
  • mybatis-plus @DS實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源原理

    mybatis-plus @DS實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源原理

    本文主要介紹了mybatis-plus @DS實(shí)現(xiàn)動(dòng)態(tài)切換數(shù)據(jù)源原理,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Java實(shí)現(xiàn)生成pdf并解決表格分割的問題

    Java實(shí)現(xiàn)生成pdf并解決表格分割的問題

    這篇文章主要為大家詳細(xì)介紹了如何利用Java實(shí)現(xiàn)生成pdf,并解決表格分割的問題,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2023-11-11
  • java生成指定范圍隨機(jī)數(shù)的多種代碼

    java生成指定范圍隨機(jī)數(shù)的多種代碼

    今天在寫代碼的時(shí)候需要用到一個(gè)生成指定范圍隨機(jī)數(shù)的函數(shù),百度了一下,發(fā)現(xiàn)了很多種方法,這里簡單為大家整理一下,方便需要的朋友
    2017-08-08
  • spring使用OXM進(jìn)行對象XML映射解析

    spring使用OXM進(jìn)行對象XML映射解析

    這篇文章主要介紹了spring使用OXM進(jìn)行對象XML映射解析,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2017-12-12
  • mybatis如何批量添加一對多中間表

    mybatis如何批量添加一對多中間表

    這篇文章主要介紹了mybatis如何批量添加一對多中間表,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Docker?DockerFile部署java?jar項(xiàng)目包及Mysql和Redis的詳細(xì)過程

    Docker?DockerFile部署java?jar項(xiàng)目包及Mysql和Redis的詳細(xì)過程

    Dockerfile是一種用于構(gòu)建Docker鏡像的文件格式,可以通過Dockerfile部署Java項(xiàng)目,這篇文章主要給大家介紹了關(guān)于Docker?DockerFile部署java?jar項(xiàng)目包及Mysql和Redis的詳細(xì)過程,需要的朋友可以參考下
    2023-12-12
  • ZooKeeper Java API編程實(shí)例分析

    ZooKeeper Java API編程實(shí)例分析

    本文主要通過實(shí)例給大家詳細(xì)分析了ZooKeeper用JAVA實(shí)現(xiàn)API編程的知識要點(diǎn)。
    2017-11-11
  • 使用Spring組合自定義的注釋 mscharhag操作

    使用Spring組合自定義的注釋 mscharhag操作

    這篇文章主要介紹了使用Spring組合自定義的注釋 mscharhag,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的工作或?qū)W習(xí)有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-03-03

最新評論