[Mybatis series] custom implementation of the interceptor plug-in Interceptor

First get familiar with the execution process of Mybatis, as shown below:
Insert picture description here

Types of

First explain that there are four specific types that can be intercepted in Mybatis:

1.Executor:拦截执行器的方法。
2.ParameterHandler:拦截参数的处理。
3.ResultHandler:拦截结果集的处理。
4.StatementHandler:拦截Sql语法构建的处理。

rule

The Intercepts annotation requires a Signature (intercept point) parameter array. Use Signature to specify which method in which object to intercept. The @Intercepts annotation is defined as follows:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Intercepts {
    
    
    /**
     * 定义拦截点
     * 只有符合拦截点的条件才会进入到拦截器
     */
    Signature[] value();
}

Signature to specify which method of that class object we need to intercept. It is defined as follows:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({
    
    })
public @interface Signature {
    
    
  /**
   * 定义拦截的类 Executor、ParameterHandler、StatementHandler、ResultSetHandler当中的一个
   */
  Class<?> type();

  /**
   * 在定义拦截类的基础之上,在定义拦截的方法
   */
  String method();

  /**
   * 在定义拦截方法的基础之上在定义拦截的方法对应的参数,
   * JAVA里面方法可能重载,故注意参数的类型和顺序
   */
  Class<?>[] args();
}

Identifies the @Interceptsuse of blocking annotation rules, a simple example is as follows:


@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})
})

Explanation:
@Intercepts: Identifies that this class is an interceptor;
@Signature: Indicate which type and method the custom interceptor needs to intercept ; -type: one of the
above four types ; -method
: corresponding to the interface Which type of method (because there may be overloaded methods);
-args: which method corresponds to the input parameters;

methodCorresponding to four types of methods:

Interception type Interception method
Executor update, query, flushStatements, commit, rollback,getTransaction, close, isClosed
ParameterHandler getParameterObject, setParameters
StatementHandler prepare, parameterize, batch, update, query
ResultSetHandler handleResultSets, handleOutputParameters

Introduction

When it comes to the practice part of custom interceptors, follow the following three steps:

  1. To implement the org.apache.ibatis.plugin.Interceptorinterface, rewrite the following methods:
public interface Interceptor {
    
    
    Object intercept(Invocation var1) throws Throwable;
    Object plugin(Object var1);
    void setProperties(Properties var1);
}
  1. Add interceptor annotations @Intercepts{...}. The specific value is set according to the above rules.
  2. Add an interceptor to the configuration file.

intercept(Invocation invocation)

From the above we have learned that the interceptor can intercept the four types of objects, where the invocationinput refers to the intercepted object.
For example: intercept the **StatementHandler#query(Statement st,ResultHandler rh)** method, then Invocation is the object.

plugin(Object target)

The function of this method is to let mybatis determine whether to intercept, and then make a decision whether to generate a proxy.

    @Override
    public Object plugin(Object target) {
    
    
    //判断是否拦截这个类型对象(根据@Intercepts注解决定),然后决定是返回一个代理对象还是返回原对象。
//故我们在实现plugin方法时,要判断一下目标类型,如果是插件要拦截的对象时才执行Plugin.wrap方法,否则的话,直接返回目标本身。
        if (target instanceof StatementHandler) {
    
    
            return Plugin.wrap(target, this);
        }
        return target;
    }

Note: The plugin method of the plug-in will be called every time an interceptor object passes, that is, the method will be called 4 times. According to @Intercepts annotation to decide whether to intercept processing.

setProperties(Properties properties)

The interceptor needs some variable objects, and this object is configurable.

Actual combat

  • Custom interceptor
@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) {
    
    

    }
}

Mainly look at the core code method intercept(): the
main purpose of this code: intercept insert and update statements, use the reflection mechanism, set the parameter rev (version number, using optimistic lock) of the insert statement, the first query, so the creation time and operation time are the same ; Update is the version number +1, unified modification of its operation time.

  • 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>
  • A
    particularly important point in application.yml is that the objects in mybatis-config must be injected into the Sprint container, otherwise it will not take effect.
...//省略其他配置
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);
        }
    }
}
  • debug
    Insert picture description here

In the above figure, you can see the main attribute values ​​stored in the BoundSql object, so when we customize the interceptor, we mainly modify the attribute values ​​of BoundSql.
The program code does not reach the position where we set the value of the reflection mechanism, test createTime=null;
Insert picture description here

Before returning, look at the value of the BoundSql object, the creation time has been assigned.
Insert picture description here

Source code: https://github.com/stream-source

Guess you like

Origin blog.csdn.net/xuan_lu/article/details/109253213