Transferred from: https://www.jianshu.com/p/0a72bb1f6a21
mybatis custom interceptor (I) basic usage
mybatis custom interceptor (II) object details
1. mybatis custom interceptor implementation steps:
- Implement the org.apache.ibatis.plugin.Interceptor interface.
- Add interceptor annotation org.apache.ibatis.plugin.Intercepts.
- Add interceptors to the configuration file.
2. In mybatis, there are four types that can be intercepted (in the order of interception):
- Executor: method to intercept the executor.
- ParameterHandler: processing of intercepting parameters.
- ResultHandler: intercepts the processing of the result set.
- StatementHandler: intercepts processing built by Sql syntax.
1. Execution sequence of different interception types:
com.galax.configuration.Aa#plugin print interceptor object order.png
2. The order in which multiple plug-ins are intercepted?
image.png
It should be noted that since interceptor Aa and interceptor Bb are intercepted StatementHandler objects, interceptor B obtains proxy objects when obtaining StatementHandler here.
3. Execution sequence of multiple plug-in plugin() and intercept() methods
First execute the plugin method of each plug-in. If the @ Intercepts annotation indicates that the object needs to be intercepted, generate a proxy object of type object. (even if the plug-in needs to Intercept objects of this type, it will still execute the plugin method of the next plug-in). Know that all plugin methods have been executed. After each Intercept method is executed.
3. Function of interceptor annotation:
The custom interceptor must use the annotation provided by mybatis to declare the type object we want to intercept.
The Mybatis plug-in should have the Intercepts [in tessypos] annotation to specify which object and which method to intercept. We know that the Plugin.wrap method will return the proxy objects of the four interface objects and intercept all methods. When the proxy object executes the corresponding method, it will call the invoke method of the InvocationHandler processor.
4. Rules for interceptor annotation:
The specific rules are as follows:
@Intercepts({ @Signature(type = StatementHandler.class, method = "query", args = ), @Signature(type = StatementHandler.class, method = "update", args = ), @Signature(type = StatementHandler.class, method = "batch", args = ) })
- @Intercepts: identifies that this class is an interceptor;
- @Signature: indicates which type and method the user-defined interceptor needs to intercept;
2.1 type: corresponds to one of the four types;
2.2 method: which method in the corresponding interface (because there may be overloaded methods);
2.3 args: which method corresponds to;
5. Interceptable method of Interceptor:
Intercepted classMethod of interception Executor update, query, flushStatements, commit, rollback,getTransaction, close, isClosed ParameterHandler getParameterObject, setParameters StatementHandler prepare, parameterize, batch, update, query ResultSetHandler handleResultSets, handleOutputParameters 2. Interceptor method2.1 official plug-in development method
@Intercepts({@Signature(type = Executor.class, method = "query", args = )}) public class TestInterceptor implements Interceptor { public Object intercept(Invocation invocation) throws Throwable { Object target = invocation.getTarget(); //Proxied object Method method = invocation.getMethod(); //Proxy method Object[] args = invocation.getArgs(); //Method parameters // do something... Method executes code block before interception Object result = invocation.proceed(); // do something... Execute code block after method interception return result; } public Object plugin(Object target) { return Plugin.wrap(target, this); } }
2.2 method of interceptor
public interface Interceptor { Object intercept(Invocation invocation) throws Throwable; Object plugin(Object target); void setProperties(Properties properties); }
2.2.1 setProperties method
If our interceptor needs some variable objects, and this object supports configurable.
Similar to @ Value("${}") in Spring application.properties File.
usage method:
<plugin interceptor="com.plugin.mybatis.MyInterceptor"> <property name="username" value="xxx"/> <property name="password" value="xxx"/> </plugin>
Method: properties.getProperty("username");
Question: but why not use @ Value("${}") to get variables directly?
Answer: because the mybatis framework itself is a framework that can be used independently, it does not do a lot of dependency injection like Spring.
2.2.2 plugin method
The purpose of this method is to let mybatis judge whether to intercept, and then decide whether to generate an agent.
@Override public Object plugin(Object target) { if (target instanceof StatementHandler) { return Plugin.wrap(target, this); } return target; }
Note that every time a interceptor object passes, it calls the plug-in plugin method, which means it calls four times. Determine whether to intercept according to the @ Intercepts annotation.
Question 1: what is the function of the Plugin.wrap(target, this) method?
Answer: judge whether to intercept this type of object (according to the @ Intercepts annotation), and then decide whether to return a proxy object or the original object.
Therefore, when implementing the plugin method, we should judge the target type. Only when it is the object to be intercepted by the plug-in can we execute the Plugin.wrap method. Otherwise, we will directly return to the target itself.
Question 2: the interceptor proxy object may go through multiple proxies. How to obtain the real interceptor object?
/** * <p> * To get the real processing object, there may be multiple agents * </p> */ @SuppressWarnings("unchecked") public static <T> T realTarget(Object target) { if (Proxy.isProxyClass(target.getClass())) { MetaObject metaObject = SystemMetaObject.forObject(target); return realTarget(metaObject.getValue("h.target")); } return (T) target; }
2.2.3 intercept (invocation) method
We know that mybatis can only intercept four types of objects. The intercept method handles the intercepted object. For example, if we want to intercept the StatementHandler#query(Statement st,ResultHandler rh) method, then the Invocation is the object, and there are three parameters in the Invocation.
- target: StatementHandler;
- method : query;
- args[]: Statement st,ResultHandler rh
org.apache.ibatis.reflection.SystemMetaObject#forObject: easy to get the value in the object.
Case: splicing parameters into sql statements.
Because the ParameterHandler interceptor has been executed, the Statement object is a fully spliced SQL Statement.
import org.apache.ibatis.executor.Executor; import org.apache.ibatis.mapping.BoundSql; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.mapping.ParameterMapping; import org.apache.ibatis.plugin.*; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.ResultHandler; import org.apache.ibatis.session.RowBounds; import org.apache.ibatis.type.TypeHandlerRegistry; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.text.DateFormat; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Properties; @Intercepts({@Signature(type = Executor.class, method = "update", args = ), @Signature(type = Executor.class, method = "query", args = )}) public class MybatisLogInterceptor implements Interceptor { private Properties properties; private static final Logger logger = LoggerFactory.getLogger(MybatisLogInterceptor.class); public Object intercept(Invocation invocation) throws Throwable { long start = 0L; String sqlId = ""; BoundSql boundSql = null; Configuration configuration = null; Object returnValue = null; try { MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0]; sqlId = mappedStatement.getId(); if(sqlId.contains("History") || sqlId.contains("Tmp")){ return invocation.proceed(); } Object parameter = null; if (invocation.getArgs().length > 1) { parameter = invocation.getArgs()[1]; } boundSql = mappedStatement.getBoundSql(parameter); configuration = mappedStatement.getConfiguration(); start = System.currentTimeMillis(); } catch (Exception e) { logger.debug("Mybatis Cause of interceptor preprocessing exception:", e); logger.error("Mybatis Cause of interceptor preprocessing exception:" + e); } returnValue = invocation.proceed(); try { long end = System.currentTimeMillis(); long time = (end - start); String sql = getSql(configuration, boundSql, sqlId, time); // if (time >= Config.SQL_WARN_TIME) { // logger.warn(sql); // } else { // logger.info(sql); // } } catch (Exception e) { logger.debug("Mybatis Reason for exception of interceptor post processing:", e); logger.error("Mybatis Reason for exception of interceptor post processing:" + e); } return returnValue; } public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) { String sql = showSql(configuration, boundSql); StringBuilder str = new StringBuilder(100); str.append("[sqlId]").append(sqlId); str.append("[SQL time consuming-").append(time).append("-[MS]"); str.append("[SQL]").append(sql); //logger.debug(SQLFormatter.format(str.toString())); logger.debug(str.toString()); return str.toString(); } private static String getParameterValue(Object obj) { String value = null; if (obj instanceof String) { value = "'" + obj.toString() + "'"; value = value.replaceAll("\\\\