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

MyBatis實(shí)現(xiàn)自定義MyBatis插件的流程詳解

 更新時(shí)間:2024年12月19日 11:57:08   作者:一名技術(shù)極客  
MyBatis的一個(gè)重要的特點(diǎn)就是插件機(jī)制,使得MyBatis的具備較強(qiáng)的擴(kuò)展性,我們可以根據(jù)MyBatis的插件機(jī)制實(shí)現(xiàn)自己的個(gè)性化業(yè)務(wù)需求,本文給大家介紹了MyBatis實(shí)現(xiàn)自定義MyBatis插件的流程,需要的朋友可以參考下

初識(shí)插件

我們在執(zhí)行查詢的時(shí)候,如果sql沒有加上分頁條件,數(shù)據(jù)量過大的話會(huì)造成內(nèi)存溢出,因此我們可以通過MyBatis提供的插件機(jī)制來攔截sql,并進(jìn)行sql改寫。MyBatis的插件是通過動(dòng)態(tài)代理來實(shí)現(xiàn)的,并且會(huì)形成一個(gè)插件鏈。原理類似于攔截器,攔截我們需要處理的對象,進(jìn)行自定義邏輯后,返回一個(gè)代理對象,進(jìn)行下一個(gè)攔截器的處理。

我們先來看下一個(gè)簡單插件的模板,首先要實(shí)現(xiàn)一個(gè)Interceptor接口,并實(shí)現(xiàn)三個(gè)方法。并加上@Intercepts注解。接下來我們以分頁插件為例將對每個(gè)細(xì)節(jié)進(jìn)行講解。

/**
 * @ClassName : PagePlugin
 * @Description : 分頁插件
 * @Date: 2020/12/29
 */
@Intercepts({})
public class PagePlugin implements Interceptor {
    
    private Properties properties;
    
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

攔截對象

在進(jìn)行插件創(chuàng)建的時(shí)候,需要指定攔截對象。@Intercepts注解指定需要攔截的方法簽名,內(nèi)容是個(gè)Signature類型的數(shù)組,而Signature就是對攔截對象的描述。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
  /**
   * Returns method signatures to intercept.
   *
   * @return method signatures
   */
  Signature[] value();
}

Signature 需要指定攔截對象中方法的信息的描述。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
  /**
   * 對象類型
   */
  Class<?> type();

  /**
   * 方法名
   */
  String method();

  /**
   * 參數(shù)類型
   */
  Class<?>[] args();
}

在MyBatis中,我們只能對以下四種類型的對象進(jìn)行攔截

  • ParameterHandler : 對sql參數(shù)進(jìn)行處理
  • ResultSetHandler : 對結(jié)果集對象進(jìn)行處理
  • StatementHandler : 對sql語句進(jìn)行處理
  • Executor : 執(zhí)行器,執(zhí)行增刪改查

現(xiàn)在我們需要對sql進(jìn)行改寫,因此可以需要攔截Executor的query方法進(jìn)行攔截

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

攔截實(shí)現(xiàn)

每個(gè)插件除了指定攔截的方法后,還需要實(shí)現(xiàn)Interceptor接口。Interceptor接口有以下三個(gè)方法。其中intercept是我們必須要實(shí)現(xiàn)的方法,在這里面我們需要實(shí)現(xiàn)自定義邏輯。其它兩個(gè)方法給出了默認(rèn)實(shí)現(xiàn)。

public interface Interceptor {

  /**
   * 進(jìn)行攔截處理
   * @param invocation
   * @return
   * @throws Throwable
   */
  Object intercept(Invocation invocation) throws Throwable;

  /**
   * 返回代理對象
   * @param target
   * @return
   */
  default Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }

  /**
   * 設(shè)置配置屬性
   * @param properties
   */
  default void setProperties(Properties properties) {
    // NOP
  }

}

因此我們實(shí)現(xiàn)intercept方法即可,因?yàn)槲覀円膶懖樵僺ql語句,因此需要攔截Executor的query方法,然后修改RowBounds參數(shù)中的limit,如果limit大于1000,我們強(qiáng)制設(shè)置為1000。

@Slf4j
@Intercepts({@Signature(type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class , ResultHandler.class})})
public class PagePlugin implements Interceptor {

    private Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        RowBounds rowBounds = (RowBounds)args[2];
        log.info("執(zhí)行前, rowBounds = [{}]", JSONUtil.toJsonStr(rowBounds));
        if(rowBounds != null){
            if(rowBounds.getLimit() > 1000){
                Field field = rowBounds.getClass().getDeclaredField("limit");
                field.setAccessible(true);
                field.set(rowBounds, 1000);
            }
        }else{
            rowBounds = new RowBounds(0 ,100);
            args[2] = rowBounds;
        }
        log.info("執(zhí)行后, rowBounds = [{}]", JSONUtil.toJsonStr(rowBounds));
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

加載流程

以上我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡單的插件,在執(zhí)行查詢的時(shí)候?qū)uery方法進(jìn)行攔截,并且修改分頁參數(shù)。但是我們現(xiàn)在還沒有進(jìn)行插件配置,只有配置了插件,MyBatis才能啟動(dòng)過程中加載插件。

xml配置插件

mybatis-config.xml中添加plugins標(biāo)簽,并且配置我們上面實(shí)現(xiàn)的plugin

<plugins>
	<plugin interceptor="com.example.demo.mybatis.PagePlugin">
	</plugin>
</plugins>

XMLConfigBuilder加載插件

在啟動(dòng)流程中加載插件中使用到SqlSessionFactoryBuilder的build方法,其中XMLConfigBuilder這個(gè)解析器中的parse()方法就會(huì)讀取plugins標(biāo)簽下的插件,并加載Configuration中的InterceptorChain中。

// SqlSessionFactoryBuilder
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
	SqlSessionFactory var5;
	try {
		XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
		var5 = this.build(parser.parse());
	} catch (Exception var14) {
		throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
	} finally {
		ErrorContext.instance().reset();

		try {
			inputStream.close();
		} catch (IOException var13) {
		}

	}

	return var5;
}

可見XMLConfigBuilder這個(gè)parse()方法就是解析xml中配置的各個(gè)標(biāo)簽。

// XMLConfigBuilder
public Configuration parse() {
	if (parsed) {
	  throw new BuilderException("Each XMLConfigBuilder can only be used once.");
	}
	parsed = true;
	parseConfiguration(parser.evalNode("/configuration"));
	return configuration;
}

private void parseConfiguration(XNode root) {
	try {
	  // issue #117 read properties first
	  // 解析properties節(jié)點(diǎn)
	  propertiesElement(root.evalNode("properties"));
	  Properties settings = settingsAsProperties(root.evalNode("settings"));
	  loadCustomVfs(settings);
	  loadCustomLogImpl(settings);
	  typeAliasesElement(root.evalNode("typeAliases"));
	  // 記載插件
	  pluginElement(root.evalNode("plugins"));
	  objectFactoryElement(root.evalNode("objectFactory"));
	  objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
	  reflectorFactoryElement(root.evalNode("reflectorFactory"));
	  settingsElement(settings);
	  // read it after objectFactory and objectWrapperFactory issue #631
	  environmentsElement(root.evalNode("environments"));
	  databaseIdProviderElement(root.evalNode("databaseIdProvider"));
	  typeHandlerElement(root.evalNode("typeHandlers"));
	  mapperElement(root.evalNode("mappers"));
	} catch (Exception e) {
	  throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
	}
}
XMLConfigBuilder 的pluginElement就是遍歷plugins下的plugin加載到interceptorChain中。

// XMLConfigBuilder
private void pluginElement(XNode parent) throws Exception {
	if (parent != null) {
	  // 遍歷每個(gè)plugin插件
	  for (XNode child : parent.getChildren()) {
		// 讀取插件的實(shí)現(xiàn)類
		String interceptor = child.getStringAttribute("interceptor");
		// 讀取插件配置信息
		Properties properties = child.getChildrenAsProperties();
		// 創(chuàng)建interceptor對象
		Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
		interceptorInstance.setProperties(properties);
		// 加載到interceptorChain鏈中
		configuration.addInterceptor(interceptorInstance);
	  }
	}
}

InterceptorChain 是一個(gè)interceptor集合,相當(dāng)于是一層層包裝,后一個(gè)插件就是對前一個(gè)插件的包裝,并返回一個(gè)代理對象。

public class InterceptorChain {

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

}

創(chuàng)建插件對象

因?yàn)槲覀冃枰獙r截對象進(jìn)行攔截,并進(jìn)行一層包裝返回一個(gè)代理類,那是什么時(shí)候進(jìn)行處理的呢?以Executor為例,在創(chuàng)建Executor對象的時(shí)候,會(huì)有以下代碼。

// Configuration
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);
}
// 創(chuàng)建插件對象
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}

創(chuàng)建完Executor對象后,就會(huì)調(diào)用interceptorChain.pluginAll()方法,實(shí)際調(diào)用的是每個(gè)Interceptor的plugin()方法。plugin()就是對目標(biāo)對象的一個(gè)代理,并且生成一個(gè)代理對象返回。而Plugin.wrap()就是進(jìn)行包裝的操作。

// Interceptor
/**
* 返回代理對象
* @param target
* @return
*/
default Object plugin(Object target) {
	return Plugin.wrap(target, this);
}

Plugin的wrap()主要進(jìn)行了以下步驟:

  • 獲取攔截器攔截的方法,以攔截對象為key,攔截方法集合為value
  • 獲取目標(biāo)對象的class對,比如Executor對象
  • 如果攔截器中攔截的對象包含目標(biāo)對象實(shí)現(xiàn)的接口,則返回?cái)r截的接口
  • 創(chuàng)建代理類Plugin對象,Plugin實(shí)現(xiàn)了InvocationHandler接口,最終對目標(biāo)對象的調(diào)用都會(huì)調(diào)用Plugin的invocate方法。
// Plugin
public static Object wrap(Object target, Interceptor interceptor) {
	// 獲取攔截器攔截的方法,以攔截對象為key,攔截方法為value
	Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
	// 獲取目標(biāo)對象的class對象
	Class<?> type = target.getClass();
	// 如果攔截器中攔截的對象包含目標(biāo)對象實(shí)現(xiàn)的接口,則返回?cái)r截的接口
	Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
	// 如果對目標(biāo)對象進(jìn)行了攔截
	if (interfaces.length > 0) {
	  // 創(chuàng)建代理類Plugin對象
	  return Proxy.newProxyInstance(
		  type.getClassLoader(),
		  interfaces,
		  new Plugin(target, interceptor, signatureMap));
	}
	return target;
}

例子

我們已經(jīng)了解MyBatis插件的配置,創(chuàng)建,實(shí)現(xiàn)流程,接下來就以一開始我們提出的例子來介紹實(shí)現(xiàn)一個(gè)插件應(yīng)該做哪些。

確定攔截對象

因?yàn)槲覀円獙Σ樵僺ql分頁參數(shù)進(jìn)行改寫,因此可以攔截Executor的query方法,并進(jìn)行分頁參數(shù)的改寫

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

實(shí)現(xiàn)攔截接口

實(shí)現(xiàn)Interceptor接口,并且實(shí)現(xiàn)intercept實(shí)現(xiàn)我們的攔截邏輯

@Slf4j
@Intercepts({@Signature(type = Executor.class,
        method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class , ResultHandler.class})})
public class PagePlugin implements Interceptor {

    private Properties properties;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        Object[] args = invocation.getArgs();
        RowBounds rowBounds = (RowBounds)args[2];
        log.info("執(zhí)行前, rowBounds = [{}]", JSONUtil.toJsonStr(rowBounds));
        if(rowBounds != null){
            if(rowBounds.getLimit() > 1000){
                Field field = rowBounds.getClass().getDeclaredField("limit");
                field.setAccessible(true);
                field.set(rowBounds, 1000);
            }
        }else{
            rowBounds = new RowBounds(0 ,100);
            args[2] = rowBounds;
        }
        log.info("執(zhí)行后, rowBounds = [{}]", JSONUtil.toJsonStr(rowBounds));
        return invocation.proceed();
    }

    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    @Override
    public void setProperties(Properties properties) {
        this.properties = properties;
    }
}

配置插件

mybatis-config.xml中配置以下插件

<plugins>
	<plugin interceptor="com.example.demo.mybatis.PagePlugin">
	</plugin>
</plugins>

測試

TTestUserMapper.java 新增selectByPage方法

List<TTestUser> selectByPage(@Param("offset") Integer offset, @Param("pageSize") Integer pageSize);

mapper/TTestUserMapper.xml 新增對應(yīng)的sql

<select id="selectByPage" resultMap="BaseResultMap">
select
<include refid="Base_Column_List" />
from t_test_user
<if test="offset != null">
  limit #{offset}, #{pageSize}
</if>
</select>

最終測試代碼,我們沒有在查詢的時(shí)候指定分頁參數(shù)。

public static void main(String[] args) {
	try {
		// 1. 讀取配置
		InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
		// 2. 創(chuàng)建SqlSessionFactory工廠
		SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
		// 3. 獲取sqlSession
		SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.SIMPLE);
		// 4. 獲取Mapper
		TTestUserMapper userMapper = sqlSession.getMapper(TTestUserMapper.class);
		// 5. 執(zhí)行接口方法
		List<TTestUser> list2 = userMapper.selectByPage(null, null);
		System.out.println("list2="+list2.size());
		// 6. 提交事物
		sqlSession.commit();
		// 7. 關(guān)閉資源
		sqlSession.close();
		inputStream.close();
	} catch (Exception e){
		log.error(e.getMessage(), e);
	}
}

最終打印的日志如下,我們可以看到rowBounds已經(jīng)被我們強(qiáng)制修改了只能查處1000條數(shù)據(jù)。

10:11:49.313 [main] INFO com.example.demo.mybatis.PagePlugin - 執(zhí)行前, rowBounds = [{"offset":0,"limit":2147483647}]
10:11:58.015 [main] INFO com.example.demo.mybatis.PagePlugin - 執(zhí)行后, rowBounds = [{"offset":0,"limit":1000}]
10:12:03.211 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
10:12:04.269 [main] DEBUG org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 749981943.
10:12:04.270 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@2cb3d0f7]
10:12:04.283 [main] DEBUG com.example.demo.dao.TTestUserMapper.selectByPage - ==>  Preparing: select id, member_id, real_name, nickname, date_create, date_update, deleted from t_test_user 
10:12:04.335 [main] DEBUG com.example.demo.dao.TTestUserMapper.selectByPage - ==> Parameters: 
list2=1000

以上就是MyBatis實(shí)現(xiàn)自定義MyBatis插件的流程詳解的詳細(xì)內(nèi)容,更多關(guān)于MyBatis自定義MyBatis插件的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • ScheduledExecutorService任務(wù)定時(shí)代碼示例

    ScheduledExecutorService任務(wù)定時(shí)代碼示例

    這篇文章主要介紹了ScheduledExecutorService任務(wù)定時(shí)代碼示例,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-01-01
  • Spring單數(shù)據(jù)源的配置詳解

    Spring單數(shù)據(jù)源的配置詳解

    spring數(shù)據(jù)源的配置網(wǎng)絡(luò)上有很多例子,這里我也來介紹一下單數(shù)據(jù)源配置的例子,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • Java深入講解SPI的使用

    Java深入講解SPI的使用

    SPI英文全稱為Service Provider Interface,顧名思義,服務(wù)提供者接口,它是jdk提供給“服務(wù)提供廠商”或者“插件開發(fā)者”使用的接口
    2022-06-06
  • 告別無盡等待:Java中的輪詢終止技巧

    告別無盡等待:Java中的輪詢終止技巧

    在Java中,輪詢是一種常見的處理方式,用于檢查某個(gè)條件是否滿足,直到滿足條件或達(dá)到一定的時(shí)間限制,本文將介紹Java中常用的輪詢結(jié)束方式,包括使用循環(huán)、定時(shí)器和線程池等方法,需要的朋友可以參考下
    2023-10-10
  • java顯示聲音波形圖示例

    java顯示聲音波形圖示例

    這篇文章主要介紹了java顯示聲音波形圖示例,需要的朋友可以參考下
    2014-05-05
  • SSH框架實(shí)現(xiàn)表單上傳圖片實(shí)例代碼

    SSH框架實(shí)現(xiàn)表單上傳圖片實(shí)例代碼

    本篇文章主要介紹了SSH框架實(shí)現(xiàn)表單上傳圖片實(shí)例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-09-09
  • Springboot實(shí)現(xiàn)Java郵件任務(wù)過程解析

    Springboot實(shí)現(xiàn)Java郵件任務(wù)過程解析

    這篇文章主要介紹了Springboot實(shí)現(xiàn)Java郵件任務(wù)過程解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • SpringTask-Timer實(shí)現(xiàn)定時(shí)任務(wù)的詳細(xì)代碼

    SpringTask-Timer實(shí)現(xiàn)定時(shí)任務(wù)的詳細(xì)代碼

    在項(xiàng)目中開發(fā)定時(shí)任務(wù)應(yīng)該一種比較常見的需求,今天通過示例代碼給大家講解SpringTask-Timer實(shí)現(xiàn)定時(shí)任務(wù)的相關(guān)知識(shí),感興趣的朋友一起看看吧
    2024-06-06
  • spring之SpEL表達(dá)式詳解

    spring之SpEL表達(dá)式詳解

    這篇文章主要介紹了spring之SpEL表達(dá)式詳解,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • java解決動(dòng)態(tài)配置字段需求問題

    java解決動(dòng)態(tài)配置字段需求問題

    這篇文章主要介紹了java解決動(dòng)態(tài)配置字段需求問題,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-05-05

最新評(píng)論

松滋市| 万荣县| 黎平县| 蓬莱市| 报价| 东宁县| 桐庐县| 台湾省| 罗定市| 章丘市| 无锡市| 乃东县| 玛多县| 哈密市| 甘德县| 车致| 叙永县| 曲沃县| 土默特左旗| 关岭| 遵义市| 电白县| 哈巴河县| 木里| 定州市| 东山县| 宣恩县| 白朗县| 张北县| 漳浦县| 新宾| 淳化县| 当阳市| 永济市| 修武县| 福泉市| 司法| 泽普县| 林甸县| 岳阳县| 钟祥市|