Executor Interceptor Advanced Tutorial - QueryInterceptor Specification
This document covers the following aspects
- 1. Introduction to Executor query method
- 2. Interceptor configuration and call order
- 3. Tips for intercepting query methods
- 4. Specification for intercepting query methods
- 5. How to configure different Executor plugins
1. Introduction to Executor query method
In the documentation section of MyBatis' interceptor, we know that the query method in Executor can be intercepted. If you have actually written the interceptor for this method, you may know that there are two query methods in Executor:
<E> List<E> query( MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; <E> List<E> query( MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
The difference between these two methods is that the first method has two parameters, CacheKey and BoundSql. In most cases, the purpose of using an interceptor is to process SQL. If we can intercept the first method, we can directly obtain the BoundSql object, and it will be easy to get the executed SQL, and we can also process SQL.
Although I think it's very good, in the Exctutor implementation provided by MyBatis, the query method with more parameters is called internally by the query method with less parameters.
existCachingExecutor
middle:
public <E> List<E> query( MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = (parameterObject); CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql); return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
existBaseExecutor
middle:
public <E> List<E> query( MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = (parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
The above two methods are the same. Since the first query method is called internally here, and all our interceptors are proxyed layer by layerCachingExecutor
Or based onBaseExecutor
, so what we can intercept is the method with fewer parameters.
The paging plug-in has always been a method with fewer parameters since the Executor intercept. However, starting from version 5.0, both methods of query can be intercepted. Before talking about this principle, let’s first understand the execution order of the interceptor.
2. Interceptor configuration and call order
The order of call of an interceptor is divided into two types. The first type is to intercept different objects. For example, intercepting Executor and intercepting StatementHandler belong to different interceptor objects. These two types of interceptors have different logic in the overall execution. During the execution of the query method in the Executor, the following code will be called:
public <E> List<E> doQuery( MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = (); StatementHandler handler = (wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
In this code, it is the StatementHandler turn to execute, which belongs to a subprocess in the Executor execution process. Therefore, when configuring these two different types of plug-ins, they must first execute the Executor interceptor before it is StatementHandler's turn. Therefore, in this case, the order of configuring the interceptors is not important, and the order of sequence is already controlled in MyBatis logic.
The order of the second interceptor refers to the same method that intercepts the same object. For example, the query method of the Executor is intercepted. At this time, the order in which you configure the interceptor will have an impact on this. Suppose there are the following interceptors, all of which are intercepted Executor's query methods.
<plugins> <plugin interceptor=".ExecutorQueryInterceptor1"/> <plugin interceptor=".ExecutorQueryInterceptor2"/> <plugin interceptor=".ExecutorQueryInterceptor3"/> </plugins>
existThere are the following methods:
public void addInterceptor(Interceptor interceptor) { (interceptor); }
MyBatis will be added to interceptorChain in the order of the interceptor configuration, and its internal content isList<Interceptor> interceptors
. See againConfiguration
Code to create the Executor in:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? : executorType; Executor executor; if ( == executorType) { executor = new BatchExecutor(this, transaction); } else if ( == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) (executor); return executor; }
Before calling, executor is the CachingExecutor or BaseExecutor-based implementation class in the previous section. Then look at the method:
public Object pluginAll(Object target) { for (Interceptor interceptor : interceptors) { target = (target); } return target; }
The order in which we configured the interceptors in the previous order is 1, 2, and 3. Here, it will also be proxied layer by layer in the order of 1, 2, and 3. The structure after proxy is as follows:
Interceptor3:{ Interceptor2: { Interceptor1: { target: Executor } } }
It should be easy to see from this structure that in the future, it will definitely be executed in the order of 3>2>1>Executor>1>2>3. Some people may not know why there will be 1>2>3 after 3>1>Executor. This is because when using a proxy, other processing can be continued after calling the proxy method. After the processing is completed, continue to return the return value of the proxy method. For example:
Interceptor3 Pre-processing Object result = Interceptor2..query(4Parameter method); Interceptor3 Follow-up processing return result;
The same logic is also given to the method:
Interceptor2 Pre-processing Object result = Interceptor1..query(4Parameter method); Interceptor2 Follow-up processing return result;
Similarly:
Interceptor1 Pre-processing Object result = (4Parameter method); Interceptor1 Follow-up processing return result;
After being superimposed together, as follows:
Interceptor3 Pre-processing Interceptor2 Pre-processing Interceptor1 Pre-processing Object result = (4Parameter method); Interceptor1 Follow-up processing Interceptor2 Follow-up processing Interceptor3 Follow-up processing return result;
So this order is 3>2>1>Executor>1>2>3.
After you figure out this logic, continue to read, because the subsequent techniques will subvert this logic, so there will be the following specifications and how to configure different plug-ins.
3. Tips for intercepting query methods
In the previous section, the usage of interceptors is the most common usage, so this kind of execution order that can be understood occurs. But this is not the case with the pagination plugin 5.0. This plugin subverts this order, and this subversion is actually very ordinary, which is also the skill to be mentioned in this section.
While I was writing MyBatis technical books (I haven't finished writing it yet, because the paging plugin took up several weeks of writing time), I was thinking about why I couldn't intercept the first query (6 parameters) method. If I could intercept this method, I could get BoundSql directly, and then deal with SQL, it would be easy to implement other operations.
In Section 1, why the first query method cannot be intercepted is because of the following code:
public <E> List<E> query( MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = (parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
now thatCachingExecutor
Or based onBaseExecutor
The implementation class is just so simple to call two methods to get BoundSql and Cachekey. Why don't we replace them directly?
So we can have interceptor usage similar to the following:
@Intercepts(@Signature(type = , method = "query", args = {, , , })) public class QueryInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = (); MappedStatement ms = (MappedStatement) args[0]; Object parameterObject = args[1]; RowBounds rowBounds = (RowBounds) args[2]; ResultHandler resultHandler = (ResultHandler) args[3]; Executor executor = (Executor) (); BoundSql boundSql = (parameterObject); //All parameters can be processed CacheKey cacheKey = (ms, parameterObject, rowBounds, boundSql); return (ms, parameterObject, rowBounds, resultHandler, cacheKey, boundSql); } @Override public Object plugin(Object target) { return (target, this); } @Override public void setProperties(Properties properties) { } }
This interceptor directly replaces part of the original Executor logic and directly calls the 6 parameter method, which leads to the subsequent methods of the 4 parameters being skipped. However, since the executor here is a proxy object, the query method with 6 parameters can be proxyed, which disrupts the execution order in the previous section.
In the example of the interceptor in the previous section, make a simple modification and replace ExecutorQueryInterceptor2 with the above QueryInterceptor, and the configuration is as follows:
<plugins> <plugin interceptor=".ExecutorQueryInterceptor1"/> <plugin interceptor=""/> <plugin interceptor=".ExecutorQueryInterceptor3"/> </plugins>
The structure after proxy is as follows:
Interceptor3:{ QueryInterceptor: { Interceptor1: { target: Executor } } }
At this time, the call order changes, and the execution order of Interceptor3 is as follows:
Interceptor3 Pre-processing Object result = (4Parameter method); Interceptor3 Follow-up processing return result;
The execution logic is as follows:
Interceptor2 Pre-processing Object result = (6Parameter method); Interceptor2 Follow-up processing return result;
In QueryInterceptor, instead of continuing to execute 4 parameter methods, 6 parameter methods were executed. However, Interceptor1 intercepts the 4 parameters method, so Interceptor1 is skipped, and the overall execution logic becomes like this:
Interceptor3 Pre-processing Interceptor2 Pre-processing Object result = (6Parameter method); Interceptor2 Follow-up processing Interceptor3 Follow-up processing return result;
If Interceptor1 intercepts a method with 6 parameters, because QueryInterceptor obtains the executor object of Interceptor1 proxy, then Interceptor1 will be executed by QueryInterceptor.
The pagination plugin is an execution logic similar to QueryInterceptor, so when you use plugins after version 5.0, if you need to configure other Executor query plugins, you will encounter some problems (you can solve it, continue reading).
If you develop the plug-in yourself, you won’t encounter any problems when developing according to the specifications in the next section. If you use plugins provided by others, following the configuration order in Section 5 can also solve the problem.
4. Specification for intercepting query methods
The logic of QueryInterceptor is to enter the method with 4 parameters and the method with 6 parameters. This processing method is not only inconvenient to use with general Excutor interceptors. When there are more than two plug-ins similar to QueryInterceptor, due to the change of the interface, similar QueryInterceptor plug-ins cannot be executed coherently. Therefore, it is necessary to solve this problem. The solution is to use unified specifications. After the specification, the QueryInterceptor is as follows:
@Intercepts( { @Signature(type = , method = "query", args = {, , , }), @Signature(type = , method = "query", args = {, , , , , }), } ) public class QueryInterceptor implements Interceptor { @Override public Object intercept(Invocation invocation) throws Throwable { Object[] args = (); MappedStatement ms = (MappedStatement) args[0]; Object parameterObject = args[1]; RowBounds rowBounds = (RowBounds) args[2]; ResultHandler resultHandler = (ResultHandler) args[3]; Executor executor = (Executor) (); CacheKey cacheKey; BoundSql boundSql; // Due to logical relationship, it will only enter once if( == 4){ //4 parameters boundSql = (parameterObject); cacheKey = (ms, parameterObject, rowBounds, boundSql); } else { //When there are 6 parameters cacheKey = (CacheKey) args[4]; boundSql = (BoundSql) args[5]; } //TODO //Note: The following method can be called multiple times according to its own logic. In the pagination plug-in, count and page are called once each. return (ms, parameterObject, rowBounds, resultHandler, cacheKey, boundSql); } @Override public Object plugin(Object target) { return (target, this); } @Override public void setProperties(Properties properties) { } }
Pay attention to two changes. The first is the method of interceptor signature intercepting 4 and 6 parameters at the same time, so that no matter which plugin is in front or behind, it will be executed.
The second change is this code:
CacheKey cacheKey; BoundSql boundSql; // Due to logical relationship, it will only enter onceif( == 4){ //4 parameters boundSql = (parameterObject); cacheKey = (ms, parameterObject, rowBounds, boundSql); } else { //When there are 6 parameters cacheKey = (CacheKey) args[4]; boundSql = (BoundSql) args[5]; }
If this plug-in is configured later and comes in through 4 parameter methods, we will get these two objects. If this plug-in is configured in front and has been processed into a 6-parameter method by other interceptors, then we can directly remove these two parameters from args and use them directly. Taking out these two parameters ensures that when other interceptors have processed these two parameters, these two parameters will continue to take effect here.
Suppose there is a sorting plug-in and a paging plug-in. After the sorting plug-in changes BoundSql to SQL with sorting, SQL will continue to be handed over to the paging plug-in for use. When paging SQL is executed in the pagination plug-in, sorting will be retained for execution. This specification ensures that both plug-ins can be executed normally.
So if you want to use this method to implement interceptors, it is recommended that you abide by this specification.
This specification is out of control for existing plug-ins, but it can still be solved through configuration order.
5. How to configure different Executor plugins
When introducing a plug-in similar to QueryInterceptor, the plug-in cannot take effect due to disrupting the original plug-in execution method, and the plug-in cannot be effective when the order of configuration Executor is incorrect.
Examples in Section 4:
<plugins> <plugin interceptor=".ExecutorQueryInterceptor1"/> <plugin interceptor=""/> <plugin interceptor=".ExecutorQueryInterceptor3"/> </plugins>
First, the execution order is 3>Query>1>Executor. Since Query is 4 or 6 parameters coming in and 6 parameters going out. Therefore, the interceptors executed before Query must have 4 parameters (Query specification interceptors can be executed one after another, and they need to be configured according to the logic) and the interceptors executed after Query must have 6 parameters.
When this order corresponds to the configuration order, that is, the configuration of 4 parameters is below the QueryInterceptor interceptor, and the configuration of 6 parameters is above the QueryInterceptor interceptor interceptor. When configuring in this order, it is possible to ensure that all interceptors are executed.
If you want to obtain SQL as executed by the pagination plug-in (QueryInterceptor specification), you have to implement it according to the QueryInterceptor specification, otherwise you can only configure it under the pagination plug-in, and you can only obtain SQL before paging.
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for your study or work. Thank you for your support. If you want to know more about it, please see the following links