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

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

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

前言

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

一、Mybatis Plugin 是什么

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

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

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

二、Mybatis Plugin 的實(shí)例

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

在直接上示例之前,我們先看看官方提供的接口 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è)括號(hào)內(nèi)	的長(zhǎng)度...
        // newsql = sql.replace(...)
        // 把新的sql通過(guò)反射重新設(shè)置回去
        Field field = boundSql.getClass().getDeclaredField("sql");
        field.setAccessible(true);
        field.set(boundSql , newSql);
        Object returnVal = invocation.proceed();
        return returnVal;
    }
}

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

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

需要注意的是 @Intercepts 注解內(nèi)支持配置 @Signature 數(shù)組,并以逗號(hào)分割。也就是說(shuō)一個(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í)從上面,案例看出,我們對(duì)插件的設(shè)置主要是通過(guò) @Intercepts 內(nèi)的 @Signature 注解實(shí)現(xiàn)的

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

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

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

  • Statementhandler
    用于處理JDBC Statement對(duì)象的相關(guān)操作,將SQL語(yǔ)句中的占位符進(jìn)行替換,然后使用Statement對(duì)象執(zhí)行SQL語(yǔ)句
  • Resultsethandler
    主要負(fù)責(zé)將JDBC返回的ResultSet結(jié)果集轉(zhuǎn)化為Java對(duì)象,然后返還給調(diào)用方
  • ParameterHandler
    主要用于處理Java對(duì)象與JDBC參數(shù)的映射,并將其轉(zhuǎn)化為JDBC參數(shù)。
  • Executor
    更頂層的設(shè)計(jì),能對(duì)上三種類進(jìn)行調(diào)用,執(zhí)行SQL語(yǔ)句,并獲取執(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 引入的一種注入方式,它可以檢索指定的類型。

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

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

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

2.2 spring 項(xiàng)目

如果還沒(méi)有使用上spring-boot,沒(méi)有所謂的自動(dòng)配置,那也無(wú)妨,只是需要手動(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);
  }
}

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

那么plugin() 方法到底做了什么呢?我們來(lái)看看回頭再來(lái)看看 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是我們的插件對(duì)象,signatureMap是插件注釋解析到的類與方法
    if (interfaces.length > 0) {
      return Proxy.newProxyInstance(
          type.getClassLoader(),
          interfaces,
          new Plugin(target, interceptor, signatureMap));
    }
    // 如果沒(méi)有,則返回原對(duì)象
    return target;
  }

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

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

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

相關(guān)文章

最新評(píng)論