MyBatis自定義實(shí)現(xiàn)攔截器插件

首先熟悉一下Mybatis的執(zhí)行過程,如下圖:

類型
先說明Mybatis中可以被攔截的類型具體有以下四種:
1.Executor:攔截執(zhí)行器的方法。
2.ParameterHandler:攔截參數(shù)的處理。
3.ResultHandler:攔截結(jié)果集的處理。
4.StatementHandler:攔截Sql語法構(gòu)建的處理。
1234
規(guī)則
Intercepts注解需要一個(gè)Signature(攔截點(diǎn))參數(shù)數(shù)組。通過Signature來指定攔截哪個(gè)對(duì)象里面的哪個(gè)方法。@Intercepts注解定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public?@interface?Intercepts?{
????/**
?????*?定義攔截點(diǎn)
?????*?只有符合攔截點(diǎn)的條件才會(huì)進(jìn)入到攔截器
?????*/
????Signature[]?value();
}
Signature來指定咱們需要攔截那個(gè)類對(duì)象的哪個(gè)方法。定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public?@interface?Signature?{
??/**
???*?定義攔截的類?Executor、ParameterHandler、StatementHandler、ResultSetHandler當(dāng)中的一個(gè)
???*/
??Class>?type();
??/**
???*?在定義攔截類的基礎(chǔ)之上,在定義攔截的方法
???*/
??String?method();
??/**
???*?在定義攔截方法的基礎(chǔ)之上在定義攔截的方法對(duì)應(yīng)的參數(shù),
???*?JAVA里面方法可能重載,故注意參數(shù)的類型和順序
???*/
??Class>[]?args();
}
標(biāo)識(shí)攔截注解@Intercepts規(guī)則使用,簡(jiǎn)單實(shí)例如下:
@Intercepts({//注意看這個(gè)大花括號(hào),也就這說這里可以定義多個(gè)@Signature對(duì)多個(gè)地方攔截,都用這個(gè)攔截器
????????@Signature(
????????????????type?=?ResultSetHandler.class,
????????????????method?=?"handleResultSets",?
????????????????args?=?{Statement.class}),
????????@Signature(type?=?Executor.class,
????????????????method?=?"query",
????????????????args?=?{MappedStatement.class,?Object.class,?RowBounds.class,?ResultHandler.class})
})
說明:@Intercepts:標(biāo)識(shí)該類是一個(gè)攔截器;
@Signature:指明自定義攔截器需要攔截哪一個(gè)類型,哪一個(gè)方法;
type:上述四種類型中的一種; method:對(duì)應(yīng)接口中的哪類方法(因?yàn)榭赡艽嬖谥剌d方法); args:對(duì)應(yīng)哪一個(gè)方法的入?yún)ⅲ?/section>
method中對(duì)應(yīng)四種的類型的方法:
| 攔截類型 | 攔截方法 |
|---|---|
| Executor | update, query, flushStatements, commit, rollback,getTransaction, close, isClosed |
| ParameterHandler | getParameterObject, setParameters |
| StatementHandler | prepare, parameterize, batch, update, query |
| ResultSetHandler | handleResultSets, handleOutputParameters |
介紹
談到自定義攔截器實(shí)踐部分,主要按照以下三步:
實(shí)現(xiàn) org.apache.ibatis.plugin.Interceptor接口,重寫以下方法:
public?interface?Interceptor?{
????Object?intercept(Invocation?var1)?throws?Throwable;
????Object?plugin(Object?var1);
????void?setProperties(Properties?var1);
}
添加攔截器注解 @Intercepts{...}。具體值遵循上述規(guī)則設(shè)置。配置文件中添加攔截器。
intercept(Invocation invocation)
從上面我們了解到interceptor能夠攔截的四種類型對(duì)象,此處入?yún)?code style="margin-right: 2px;margin-left: 2px;font-family: 'Operator Mono', Consolas, Monaco, Menlo, monospace;word-break: break-all;background: rgb(248, 245, 236);color: rgb(255, 53, 2);line-height: 1.5;font-size: 90%;padding: 3px 5px;border-radius: 2px;">invocation便是指攔截到的對(duì)象。舉例說明:攔截StatementHandler#query(Statement st,ResultHandler rh)方法,那么Invocation就是該對(duì)象。
plugin(Object target)
這個(gè)方法的作用是就是讓mybatis判斷,是否要進(jìn)行攔截,然后做出決定是否生成一個(gè)代理。
@Override
public?Object?plugin(Object?target)?{
??//判斷是否攔截這個(gè)類型對(duì)象(根據(jù)@Intercepts注解決定),然后決定是返回一個(gè)代理對(duì)象還是返回原對(duì)象。
??//故我們?cè)趯?shí)現(xiàn)plugin方法時(shí),要判斷一下目標(biāo)類型,如果是插件要攔截的對(duì)象時(shí)才執(zhí)行Plugin.wrap方法,否則的話,直接返回目標(biāo)本身。
??if?(target?instanceof?StatementHandler)?{
????return?Plugin.wrap(target,?this);
??}
??return?target;
}
注意:每經(jīng)過一個(gè)攔截器對(duì)象都會(huì)調(diào)用插件的plugin方法,也就是說,該方法會(huì)調(diào)用4次。根據(jù)@Intercepts注解來決定是否進(jìn)行攔截處理。
setProperties(Properties properties)
攔截器需要一些變量對(duì)象,而且這個(gè)對(duì)象是支持可配置的。
實(shí)戰(zhàn)
自定義攔截器
@Intercepts(value?=?{@Signature(type?=?StatementHandler.class,?method?=?"prepare",?args?=?{Connection.class,?Integer.class})})
public?class?MyInterceptor?implements?Interceptor?{
????@Override
????public?Object?intercept(Invocation?invocation)?throws?Throwable?{
????????StatementHandler?statementHandler?=?(StatementHandler)?invocation.getTarget();
????????BoundSql?boundSql?=?statementHandler.getBoundSql();
????????Object?obj?=?boundSql.getParameterObject();
????????String?sql?=?boundSql.getSql();
????????if?(sql.trim().toUpperCase().startsWith("INSERT"))?{
????????????ReflectUtil.setFieldValue(obj,?"rev",?0);
????????????ReflectUtil.setFieldValue(obj,?"createTime",?new?Date());
????????????ReflectUtil.setFieldValue(obj,?"operateTime",?new?Date());
????????????ReflectUtil.setFieldValue(boundSql,"parameterObject",?obj);
????????}?else?if?(sql.trim().toUpperCase().startsWith("UPDATE"))?{
????????????sql?=?sql.replaceAll("?set?",?"?SET?")
????????????????????.replaceAll("?Set?",?"?SET?")
????????????????????.replaceAll("?SET?",?"?SET?rev?=?rev+1,?operate_time?=?NOW(),?");
????????????ReflectUtil.setFieldValue(boundSql,"sql",?sql);
????????}
????????return?invocation.proceed();
????}
????@Override
????public?Object?plugin(Object?o)?{
????????return?Plugin.wrap(o,?this);
????}
????@Override
????public?void?setProperties(Properties?properties)?{
????}
}
主要看下核心代碼方法intercept():
這段代碼主要目的:攔截insert和update語句,利用反射機(jī)制,設(shè)置insert語句的參數(shù)rev(版本號(hào),利用樂觀鎖),第一次查詢,故創(chuàng)建時(shí)間和操作時(shí)間相同;update是將版本號(hào)+1,統(tǒng)一修改其操作時(shí)間。
mybatis-config
configuration?PUBLIC?"-//mybatis.org//DTD?Config?3.0//EN"?"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>????
?<plugins>
????????<plugin?interceptor="com.qxy.mybatis.interceptor.MyInterceptor"/>
????plugins>
configuration>
application.yml 特別重要的一點(diǎn),一定將mybatis-config中的對(duì)象注入到Sprint容器中,否則不會(huì)生效。
...//省略其他配置
mybatis:
??config-location:?classpath:/mybatis-config.xml
ReflectUtil
public?class?ReflectUtil?{
????private?ReflectUtil()?{}
????/**
?????*?利用反射獲取指定對(duì)象的指定屬性
?????*?@param?obj?目標(biāo)對(duì)象
?????*?@param?fieldName?目標(biāo)屬性
?????*?@return?目標(biāo)字段
?????*/
????private?static?Field?getField(Object?obj,?String?fieldName)?{
????????Field?field?=?null;
????????for?(Class>?clazz?=?obj.getClass();?clazz?!=?Object.class;?clazz?=?clazz.getSuperclass())?{
????????????try?{
????????????????field?=?clazz.getDeclaredField(fieldName);
????????????????break;
????????????}?catch?(NoSuchFieldException?e)?{
????????????????//這里不用做處理,子類沒有該字段,可能父類有,都沒有就返回null
????????????}
????????}
????????return?field;
????}
????/**
?????*?利用反射設(shè)置指定對(duì)象的指定屬性為指定的值
?????*?@param?obj?目標(biāo)對(duì)象
?????*?@param?fieldName?目標(biāo)屬性
?????*?@param?fieldValue?目標(biāo)值
?????*/
????public?static?void?setFieldValue(Object?obj,?String?fieldName,?Object?fieldValue)?throws?IllegalAccessException?{
????????Field?field?=?getField(obj,?fieldName);
????????if?(field?!=?null)?{
????????????field.setAccessible(true);
????????????field.set(obj,?fieldValue);
????????}
????}
}
debug 
上圖中能夠看到BoundSql對(duì)象中主要存儲(chǔ)的屬性值,所以我們自定義攔截器時(shí),主要針對(duì)BoundSql的屬性值進(jìn)行修改。程序代碼沒有走到我們反射機(jī)制設(shè)置值的位置,測(cè)試createTime=null;

返回之前,看下BoundSql對(duì)象的值,創(chuàng)建時(shí)間已被賦值。


