01
mybatisプラグインメカニズムとは何ですか
mybatisフレームワークは、mybatisフレームワークのプラグインメカニズムであるインターセプターを提供することにより、ユーザーが元のフレームワークの機能を拡張または変更することをサポートします。
02
傍受をサポートする方法
エグゼキュータ(更新、クエリ、コミット、ロールバックなど):エグゼキュータはSQLエグゼキュータです。
StatementHandler(prepare、parameterize、batch、update、queryなどのメソッド): StatementHandlerは、Statementオブジェクトを操作してデータベースと通信し、ParameterHandlerとResultSetHandlerを呼び出してパラメーターをマップし、結果をエンティティークラスにバインドします。
ParameterHandler(getParameterObject、setParametersメソッド): ParameterHandlerは、SQLステートメントのパラメーターを処理するために使用されます。
ResultSetHandler(handleResultSets、handleOutputParametersなど): ResultSetHandlerは、SQLの実行が完了した後に返される結果セットのマッピングを処理するために使用されます。
03
プラグインメカニズムのアプリケーションシナリオ
パフォーマンス監視
SQLステートメント実行のパフォーマンスを監視するために、Executorクラスの更新、クエリ、およびその他のメソッドをインターセプトし、ログを使用して各メソッドの実行時間を記録できます。パフォーマンス監視スイッチを実際の実稼働環境に設定して、パフォーマンス監視によって通常のビジネス応答速度が低下するのを防ぐことができます。
白黒リスト機能
一部のビジネスシステムでは、実稼働環境の単一テーブル内のデータ量が膨大であり、一部のSQLステートメントを実稼働環境で実行することは許可されていません。SQLステートメントをインターセプトし、Executorクラスの更新、クエリ、およびその他のメソッドをインターセプトすることで、白黒リストのSQLステートメントまたはキーワードと比較して、SQLステートメントの実行を続行するかどうかを決定できます。
パブリックフィールドの均一な割り当て
通常、ビジネスシステムには、作成者、作成時間、変更者、変更時間の4つのフィールドがあります。これらの4つのフィールドの割り当ては、実際にはDAOレイヤーでインターセプトして均一に処理できます。mybatisプラグインを使用して、Executorクラスのupdateメソッドをインターセプトし、関連するパラメーターを均一に割り当てることができます。
その他
Mybatisはまだ非常に拡張性があり、プラグインメカニズムに基づいて、実行段階、パラメーター処理段階、文法構築段階、結果セット処理段階など、SQL実行のさまざまな段階を基本的に制御でき、柔軟に使用できます。プロジェクトによると。
04
SQL実行時間統計デモ
まず、SQLProcessTimeInterceptorをカスタマイズして、エグゼキューターレベルでインターセプトし、SQL実行時間をカウントします。インターセプターインターフェイスを継承することに加えて、ユーザー定義のインターセプターは、識別のために2つのアノテーション@Interceptsと@Signatureを使用する必要もあります。@Interceptsアノテーションは、@ Signatureアノテーションのリストを指定します。各@Signatureアノテーションは、インターセプトする必要のあるメソッド情報を識別します。@ Signatureアノテーションのtype属性は、インターセプトする必要のあるタイプとメソッド属性を指定するために使用されます。インターセプトする必要のあるタイプを指定するために使用されます。メソッド、args属性は、インターセプトされたメソッドのパラメーターリストを指定します。Javaにはオーバーロードの概念があるため、type、method、およびargsの3つの属性を介して唯一のメソッドを識別できます。
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;
@Slf4j
@Intercepts({
@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}),
@Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
RowBounds.class, ResultHandler.class})})
public class SqlProcessTimeInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
log.info("start time :" + System.currentTimeMillis());
Object result = invocation.proceed();
long end = System.currentTimeMillis();
log.info("end time :" + end);
log.info("process time is :" + (end - start) + "ms");
return result;
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
オブジェクトインターセプト(呼び出し呼び出し)は、インターセプトロジックが実装される場所です。内部的には、invocation.proceed()を介して責任のチェーンを明示的にプロモートする必要があります。つまり、ターゲットメソッドをインターセプトするために次のインターセプターが呼び出されます。
オブジェクトプラグイン(Objecttarget)は、現在のインターセプターを使用してターゲットターゲットへのプロキシを生成します。これは、実際にはPlugin.wrap(target、this)を介して実行され、ターゲットターゲットとインターセプターthisをラッパー関数に渡します。
setProperties(Properties properties)は、追加のパラメーターを設定するために使用されます。パラメーターはインターセプターのPropertiesノードで構成され、プロパティはコードで構成することもできます。
次に、mybatisフレームワークが初期化されるときに、プルの構成にカスタムSQLProcessTimeInterceptorを追加します。
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MybatisConfiguration {
@Bean
ConfigurationCustomizer mybatisConfigurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.addInterceptor(new SqlProcessTimeInterceptor());
}
};
}
}
最後に、単体テストを実行して、傍受が成功したかどうかを確認します。
//単元と试@Testpublicvoid selectUserById(){ユーザーuser = userMapper.selectUserById(1L); Assert.assertNotNull(user);}
//実行結果の開始時間:1611127784286終了時間:1611127784703処理時間:417ms
05
mybatisプラグインメカニズムの原理
上記のデモはエグゼキュータをインターセプトするため、次の主成分分析はエグゼキュータのインターセプトに基づいています。ParameterHandler、ResultHandler、ResultSetHandler、StatementHandlerのインターセプトプロセスの原則は似ています。
1.カスタムインターセプターをmybaitsフレームワークに構成して、mybatisフレームワークが初期化時にカスタムインターセプターをConfiguration.interceptorChainに追加するようにします。
構成するには2つの方法があります。
xml用にファイルを構成する1つの方法: Mybatisが初期化されると、XMLConfigBuilder.pluginElement(XNodeの親)を介してxmlを解析し、InterceptorをinterceptorChainに追加します。構成方法は次のとおりです。
<!-mybatis-config.xml-> <plugins> <plugininterceptor = "カスタムインターセプタークラスのフルパス"> <property name = "propertyKey" value = "propertyValue" /> </ plugin> </ plugins>
もう1つの方法は、構成クラスを@Configurationアノテーションで定義することです。xml構成ファイルを置き換えることができます。注釈付きクラスには1つ以上の@Beanアノテーション付きメソッドが含まれ、これらのメソッドはAnnotationConfigApplicationContextまたはAnnotationConfigWebApplicationContextクラスによってスキャンされ、Beanのビルドに使用されます。定義、Springコンテナを初期化します。これは、この記事のデモで採用されている構成方法です。
どちらのメソッドも、最終的にInterceptorChain.addInterceptor(インターセプターインターセプター)メソッドを呼び出します。
//構成クラスpublicvoid addInterceptor(Interceptorinterceptor){interceptorChain.addInterceptor(interceptor); }
// InterceptorChainクラスpublicvoid addInterceptor(Interceptorinterceptor){interceptors.add(interceptor); }
2.次の図は、mybatisフレームワークによって実行されるSQL操作の要求フロー図を示しています。エグゼキュータは、SQL操作をstatementHandlerに委任して処理します。statementHandlerは、SQLパラメーターの処理のためにParamenterHanderを呼び出してから、SQLを実行するためにstatementを呼び出します。実行が完了すると、ResultSet結果セットが返され、次にResultSetHandlerが結果セットを処理します(たとえば、返された結果とエンティティクラス間のマッピング)。
3. Mybatisフレームワークでは、エグゼキューターオブジェクトはConfiguration.newExecutor()を介して生成され、エグゼキューターのプロキシオブジェクトは、生成プロセス中にpluinAll()メソッドを介して生成され、インターセプターからエグゼキューターへ。コードを参照してください:
//構成クラスpublicExecutor newExecutor(Transaction transaction、ExecutorType executorType){executorType = executorType == null?defaultExecutorType:executorType; executorType = executorType == null?ExecutorType.SIMPLE:executorType; エグゼキュータエグゼキュータ; 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); } //生成executor的代理象executor =(Executor)interceptorChain.pluginAll(executor); エグゼキュータを返します。}
pluginAllメソッドを入力すると、メソッドがInterceptorChainのインターセプターコレクションをトラバースし、インターセプターのプラグインメソッドを呼び出すことがわかります。生成されたプロキシオブジェクトはターゲットに再割り当てされることに注意してください。複数のインターセプターがある場合、生成されたプロキシオブジェクトは別のプロキシオブジェクトによってプロキシされ、プロキシチェーンを形成します。したがって、プログラムのパフォーマンスに影響を与えるほど多くのネストレベルを回避するために、プラグインを定義しすぎないようにする必要があります。
// InterceptorChainクラスpublicObject pluginAll(Object target){//(Interceptorinterceptor:interceptors)のインターセプターコレクションをトラバースします{//カスタムInterceptorのプラグインメソッドを呼び出しますtarget = Interceptor.plugin(target);} return target;}
カスタムプラグインのinterceptor.pluginメソッドは、通常、mybatis:Plugin.wrap()によって提供されるツールメソッドの呼び出しを検討します。このクラスは、InvocationHandlerインターフェイスを実装します。プロキシエグゼキュータオブジェクトが呼び出されると、実際のInterceptor.intercept()が実行されるplugin.invoke()メソッドがトリガーされます。
//プラグインクラスpublicstatic Object wrap(Object target、Interceptorinterceptor){//カスタムInterceptor Map <Class <?>、Set <Method >>の@Signatureアノテーション情報を取得signatureMap = getSignatureMap(interceptor); //プロキシオブジェクトを取得type Class <?> type = target.getClass(); Class <?> [] interfaces = getAllInterfaces(type、signatureMap); if(interfaces.length> 0){// jdk動的プロキシを介してプロキシオブジェクトを生成し、カスタム関数を変更するプロキシオブジェクトに組み込まれますreturnProxy.newProxyInstance(type.getClassLoader()、interfaces、new Plugin(target、interceptor、signatureMap));} return target;}
getSignatureMapメソッド:最初にインターセプタークラスの@Interceptorsアノテーションを取得し、次にこのアノテーションの属性@Signatureアノテーションコレクションを取得し、次にこのコレクションをトラバースし、トラバース時に@Signatureアノテーションのtype属性(クラスタイプ)を取り出します。次に、このタイプに従って、method属性とargs属性を持つメソッドが取得されます。@Interceptorsアノテーションの@Signature属性は属性であるため、最終的には、タイプがキー、値がセットのMap構造を返します。
private static Map <Class <?>、Set <Method >> getSignatureMap(Interceptorinterceptor){インターセプトinterceptsAnnotation = Interceptor.getClass()。getAnnotation(Intercepts.class); //#251を発行if(interceptsAnnotation == null){throw new PluginException( "インターセプターに@Interceptsアノテーションが見つかりませんでした" + interceptor.getClass()。getName()); } Signature [] sigs = InterceptsAnnotation.value(); Map <Class <?>、Set <Method >> SignatureMap = new HashMap <Class <?>、Set <Method >>(); for(Signature sig:sigs){Set <Method>メソッド= signatureMap.get(sig.type()); if(methods == null){methods = new HashSet <Method>(); SignatureMap.put(sig.type()、methods); } try {メソッドmethod = sig.type()。getMethod(sig.method()、sig.args()); methods.add(method); } catch(NoSuchMethodException e){throw new PluginException( "Could not find method on" + sig.type()+ "named" + sig.method()+ "。原因:" + e、e); }} returnsignatureMap; }
Plugin.invoke()メソッドは、実際のカスタムプラグインロジックが呼び出される場所です。
//プラグインクラスpublicObject invoke(Object proxy、Method method、Object [] args)throws Throwable {try {Set <Method> methods = SignatureMap.get(method.getDeclaringClass()); //現在のメソッドメソッドがin self定義クラス@Interceptsアノテーションの@Signatureリストで、現在のメソッドをインターセプトする必要がある場合、Interceptor.interceptメソッドが実行されますif(methods!= null && methods.contains(method)){//追加関数カスタムインターセプターで実行されますローカルreturninterceptor.intercept(new Invocation(target、method、args));} //現在のメソッドをインターセプトする必要がない場合は、直接呼び出されてreturn method.invoke(target 、args);} catch(Exception e){throw ExceptionUtil.unwrapThrowable(e);}}
Interceptor.intercept (new Invocation(target、method、args))のパラメーターInvocationオブジェクトは、ターゲットクラス、ターゲットメソッド、およびメソッドパラメーターをカプセル化します。カスタムインターセプターのインターセプトメソッドでは、invocation.process()メソッドを実行することを忘れないでください。そうしないと、責任のチェーン全体が中断されます。
public class Invocation {
private final Object target;
private final Method method;
private final Object[] args;
......
.
.
......
//processed方法,触发被代理类目标方法的执行
public Object proceed() throws InvocationTargetException, IllegalAccessException {
return method.invoke(target, args);
}
}
これまでのところ、mybatisプラグインメカニズムの使用法と操作メカニズムの紹介は完了しています。
06
総括する
この記事では、mybatisフレームワークのプラグインメカニズムのアプリケーションシナリオと動作原理を紹介します。プラグインモジュールの実現アイデアは、デザインモードの責任チェーンモードとエージェントモードに基づいています。これにより、オブジェクト間の結合が減少し、システムのスケーラビリティが向上し、オープンとクローズの原則が満たされます。プラグインのアイデアは、日常の研究開発作業に柔軟に適用できます。