mybatis - basic usage of custom interceptors


Transferred from:

mybatis custom interceptor (I) basic usage
mybatis custom interceptor (II) object details

1. Interceptor comments

1. mybatis custom interceptor implementation steps:

  1. Implement the org.apache.ibatis.plugin.Interceptor interface.
  2. Add interceptor annotation org.apache.ibatis.plugin.Intercepts.
  3. Add interceptors to the configuration file.

2. In mybatis, there are four types that can be intercepted (in the order of interception):

  1. Executor: method to intercept the executor.
  2. ParameterHandler: processing of intercepting parameters.
  3. ResultHandler: intercepts the processing of the result set.
  4. 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?




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:

    @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
    @Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
    @Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})


  1. @Intercepts: identifies that this class is an interceptor;
  2. @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 method

2.1 official plug-in development method

@Intercepts({@Signature(type = Executor.class, method = "query",
        args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
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 File.
usage method:

<plugin interceptor="com.plugin.mybatis.MyInterceptor">
     <property name="username" value="xxx"/>
     <property name="password" value="xxx"/>


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.

    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>
    public static <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue(""));
        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 = {MappedStatement.class, Object.class}),
        @Signature(type = Executor.class, method = "query", args = {MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class})})
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 {
            // }
        } 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("[SQL time consuming-").append(time).append("-[MS]");
        return str.toString();

    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "'" + obj.toString() + "'";
            value = value.replaceAll("\\\\", "\\\\\\\\");
            value = value.replaceAll("\\$", "\\\\\\$");
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "'" + formatter.format(obj) + "'";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";

        return value;

    public static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));

            } else {
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
        return sql;

    public Object plugin(Object target) {
        return Plugin.wrap(target, this);

    public void setProperties(Properties properties0) { = properties0;



2. MappedStatement.class

A MappedStatement object corresponds to a select/update/insert/delete node in Mapper configuration file. It mainly describes an sql statement. Its properties are:

  //Add a namespace to the id attribute in the node
  private String id;
  //Directly from node attributes
  private Integer fetchSize;
  //Directly from node attributes
  private Integer timeout;
  private StatementType statementType;
  private ResultSetType resultSetType;
  //Corresponding to an SQL statement
  private SqlSource sqlSource;
  //Each statement has a cache, if any.
  private Cache cache;
  //This is out of date
  private ParameterMap parameterMap;
  private List<ResultMap> resultMaps;
  private boolean flushCacheRequired;
  private boolean useCache;
  private boolean resultOrdered;
  //Type of SQL, select/update/insert/detete
  private SqlCommandType sqlCommandType;
  private KeyGenerator keyGenerator;
  private String[] keyProperties;
  private String[] keyColumns;
  //Is there an internal mapping
  private boolean hasNestedResultMaps;
  private String databaseId;
  private Log statementLog;
  private LanguageDriver lang;
  private String[] resultSets;




Posted on Tue, 09 Nov 2021 15:02:22 -0500 by jmakeig