以下に示すように、最初にMybatisの実行プロセスをよく理解してください。
の種類
最初に、Mybatisで傍受できる4つの特定のタイプがあることを説明します。
1.Executor:拦截执行器的方法。
2.ParameterHandler:拦截参数的处理。
3.ResultHandler:拦截结果集的处理。
4.StatementHandler:拦截Sql语法构建的处理。
ルール
Interceptsアノテーションには、Signature(インターセプトポイント)パラメーター配列が必要です。署名を使用して、インターセプトするオブジェクトのメソッドを指定します。@Interceptsアノテーションは次のように定義されています。
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
/**
* 定义拦截点
* 只有符合拦截点的条件才会进入到拦截器
*/
Signature[] value();
}
そのクラスオブジェクトのどのメソッドをインターセプトする必要があるかを指定するための署名。これは次のように定義されます。
@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:
上記の4つのタイプの1つ; -method
:インターフェースに対応しますどのタイプのメソッド(オーバーロードされたメソッドがある可能性があるため);
-args:どのメソッドが入力パラメーターに対応するか。
method
4種類の方法に対応します。
傍受タイプ | 傍受方法 |
---|---|
エグゼキュータ | update、query、flushStatements、commit、rollback、getTransaction、close、isClosed |
ParameterHandler | getParameterObject、setParameters |
StatementHandler | 準備、パラメータ化、バッチ処理、更新、クエリ |
ResultSetHandler | handleResultSets、handleOutputParameters |
前書き
カスタムインターセプターの練習部分に関しては、次の3つの手順に従ってください。
org.apache.ibatis.plugin.Interceptor
インターフェイスを実装するには、次のメソッドを書き直します。
public interface Interceptor {
Object intercept(Invocation var1) throws Throwable;
Object plugin(Object var1);
void setProperties(Properties var1);
}
- インターセプター注釈を追加し
@Intercepts{...}
ます。具体的な値は、上記のルールに従って設定されます。 - 構成ファイルにインターセプターを追加します。
インターセプト(呼び出し呼び出し)
上記から、インターセプターは4種類のオブジェクトをインターセプトできることがわかりました。ここで、invocation
入力はインターセプトされたオブジェクトを参照します。
例:** StatementHandler#query(Statement st、ResultHandler rh)**メソッドをインターセプトすると、Invocationがオブジェクトになります。
プラグイン(オブジェクトターゲット)
このメソッドの機能は、mybatisにインターセプトするかどうかを決定させてから、プロキシを生成するかどうかを決定させることです。
@Override
public Object plugin(Object target) {
//判断是否拦截这个类型对象(根据@Intercepts注解决定),然后决定是返回一个代理对象还是返回原对象。
//故我们在实现plugin方法时,要判断一下目标类型,如果是插件要拦截的对象时才执行Plugin.wrap方法,否则的话,直接返回目标本身。
if (target instanceof StatementHandler) {
return Plugin.wrap(target, this);
}
return target;
}
注:プラグインのプラグインメソッドは、インターセプターオブジェクトが通過するたびに呼び出されます。つまり、メソッドは4回呼び出されます。@Interceptsアノテーションによると、処理をインターセプトするかどうかを決定します。
setProperties(プロパティプロパティ)
インターセプターにはいくつかの可変オブジェクトが必要であり、このオブジェクトは構成可能です。
実際の戦闘
- カスタムインターセプター
@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()
:
このコードの主な目的:挿入ステートメントと更新ステートメントをインターセプトし、リフレクションメカニズムを使用し、挿入ステートメントのパラメーターrev(バージョン番号、オプティミスティックロックを使用)を設定します。これにより、作成時間と操作時間が同じになります。 ;更新はバージョン番号+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);
}
}
}
- デバッグ
上の図では、BoundSqlオブジェクトに格納されている主な属性値を確認できるため、インターセプターをカスタマイズするときは、主にBoundSqlの属性値を変更します。
プログラムコードは、リフレクションメカニズムの値を設定する位置に到達しません。testcreateTime= null;
戻る前に、BoundSqlオブジェクトの値を確認してください。作成時間が割り当てられています。
ソースコード:https://github.com/stream-source