目錄
- 型別
- 規則
- 介紹
- intercept(Invocation invocation)
- plugin(Object target)
- setProperties(Properties properties)
- 實戰
首先熟悉一下Mybatis的執行程序,如下圖:

型別
先說明Mybatis中可以被攔截的型別具體有以下四種:
1.Executor:攔截執行器的方法,
2.ParameterHandler:攔截引數的處理,
3.ResultHandler:攔截結果集的處理,
4.StatementHandler:攔截Sql語法構建的處理,
規則
Intercepts注解需要一個Signature(攔截點)引數陣列,通過Signature來指定攔截哪個物件里面的哪個方法,@Intercepts注解定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
/**
* 定義攔截點
* 只有符合攔截點的條件才會進入到攔截器
*/
Signature[] value();
}
Signature來指定咱們需要攔截那個類物件的哪個方法,定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({})
public @interface Signature {
/**
* 定義攔截的類 Executor、ParameterHandler、StatementHandler、ResultSetHandler當中的一個
*/
Class<?> type();
/**
* 在定義攔截類的基礎之上,在定義攔截的方法
*/
String method();
/**
* 在定義攔截方法的基礎之上在定義攔截的方法對應的引數,
* JAVA里面方法可能多載,故注意引數的型別和順序
*/
Class<?>[] args();
}
標識攔截注解@Intercepts規則使用,簡單實體如下:
@Intercepts({//注意看這個大花括號,也就這說這里可以定義多個@Signature對多個地方攔截,都用這個攔截器
@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:標識該類是一個攔截器;
@Signature:指明自定義攔截器需要攔截哪一個型別,哪一個方法;
- type:上述四種型別中的一種;
- method:對應介面中的哪類方法(因為可能存在多載方法);
- args:對應哪一個方法的入參;
method中對應四種的型別的方法:
| 攔截型別 | 攔截方法 |
|---|---|
| Executor | update, query, flushStatements, commit, rollback,getTransaction, close, isClosed |
| ParameterHandler | getParameterObject, setParameters |
| StatementHandler | prepare, parameterize, batch, update, query |
| ResultSetHandler | handleResultSets, handleOutputParameters |
介紹
談到自定義攔截器實踐部分,主要按照以下三步:
- 實作
org.apache.ibatis.plugin.Interceptor介面,重寫以下方法:
public interface Interceptor {
Object intercept(Invocation var1) throws Throwable;
Object plugin(Object var1);
void setProperties(Properties var1);
}
- 添加攔截器注解
@Intercepts{...},具體值遵循上述規則設定, - 組態檔中添加攔截器,
intercept(Invocation invocation)
從上面我們了解到interceptor能夠攔截的四種型別物件,此處入參invocation便是指攔截到的物件,
舉例說明:攔截**StatementHandler#query(Statement st,ResultHandler rh)**方法,那么Invocation就是該物件,
plugin(Object target)
這個方法的作用是就是讓mybatis判斷,是否要進行攔截,然后做出決定是否生成一個代理,
@Override
public Object plugin(Object target) {
//判斷是否攔截這個型別物件(根據@Intercepts注解決定),然后決定是回傳一個代理物件還是回傳原物件,
//故我們在實作plugin方法時,要判斷一下目標型別,如果是插件要攔截的物件時才執行Plugin.wrap方法,否則的話,直接回傳目標本身,
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
注意:每經過一個攔截器物件都會呼叫插件的plugin方法,也就是說,該方法會呼叫4次,根據@Intercepts注解來決定是否進行攔截處理,
setProperties(Properties properties)
攔截器需要一些變數物件,而且這個物件是支持可配置的,
實戰
- 自定義攔截器
@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陳述句,利用反射機制,設定insert陳述句的引數rev(版本號,利用樂觀鎖),第一次查詢,故創建時間和操作時間相同;update是將版本號+1,統一修改其操作時間,
- mybatis-config
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE 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
特別重要的一點,一定將mybatis-config中的物件注入到Sprint容器中,否則不會生效,
...//省略其他配置
mybatis:
config-location: classpath:/mybatis-config.xml
- ReflectUtil
public class ReflectUtil {
private ReflectUtil() {}
/**
* 利用反射獲取指定物件的指定屬性
* @param obj 目標物件
* @param fieldName 目標屬性
* @return 目標欄位
*/
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;
}
/**
* 利用反射設定指定物件的指定屬性為指定的值
* @param obj 目標物件
* @param fieldName 目標屬性
* @param fieldValue 目標值
*/
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物件中主要存盤的屬性值,所以我們自定義攔截器時,主要針對BoundSql的屬性值進行修改,
程式代碼沒有走到我們反射機制設定值的位置,測驗createTime=null;

回傳之前,看下BoundSql物件的值,創建時間已被賦值,

源代碼:https://github.com/stream-source
轉載請註明出處,本文鏈接:https://www.uj5u.com/qianduan/193488.html
標籤:其他
上一篇:【演算法-Java實作】組合總和
