mybatis Implementation SQL Query Interception Modification Detailed Explanation

  • 2021-12-05 07:44:47
  • OfStack

Preface

One of the functions of interceptors is that we can intercept the calls of certain methods. We can choose to add some logic before and after the execution of these intercepted methods, or we can execute our own logic when executing these intercepted methods instead of executing the intercepted methods.

The Mybatis interceptor was originally designed to allow users to implement their own logic at some point without having to move the inherent logic of Mybatis. For example, I want to perform a fixed operation for all SQL, perform a security check for SQL queries, or log related SQL queries, and so on.

Mybatis provides us with an Interceptor interface, which can implement custom interceptors.


 public interface Interceptor {
 Object intercept(Invocation invocation) throws Throwable;
 Object plugin(Object target);
 void setProperties(Properties properties);
}

The interface contains three method definitions

The intercept method is a specific processing method for intercepting objects, and the incoming Invocation contains the strength of intercepting target classes, intercepting methods and parameter groups of methods. Execute the primitive function using procced of Invocation.

plugin executes to judge whether to intercept, if not intercept, directly return to target, if intercept is needed, call wrap static method in Plugin class, if the current interceptor realizes any interface, return 1 proxy object, otherwise directly return (recall the design of proxy mode). The proxy object is actually an instance of the Plugin class that implements the InvocationHandler interface, which contains only the invoke method for callback methods.

When the interface method of the proxy object is executed, the invoke method of Plugin is called, which packages the object, method, and parameters to be executed as an Invocation object and passes it to the interceptor's intercept method. Invocation defines an procced method to execute the intercepted original method.

Plugin class definition


public class Plugin implements InvocationHandler {
 
 private Object target;
 private Interceptor interceptor;
 private Map, Set> signatureMap;
 
 private Plugin(Object target, Interceptor interceptor, Map, Set> signatureMap) {
  this.target = target;
  this.interceptor = interceptor;
  this.signatureMap = signatureMap;
 }
 
 public static Object wrap(Object target, Interceptor interceptor) {
  Map, Set> signatureMap = getSignatureMap(interceptor);
  Class type = target.getClass();
  Class[] interfaces = getAllInterfaces(type, signatureMap);
  if (interfaces.length > 0) {
   return Proxy.newProxyInstance(
     type.getClassLoader(),
     interfaces,
     new Plugin(target, interceptor, signatureMap));
  }
  return target;
 }
 
 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  try {
   Set methods = signatureMap.get(method.getDeclaringClass());
   if (methods != null && methods.contains(method)) {
    return interceptor.intercept(new Invocation(target, method, args));
   }
   return method.invoke(target, args);
  } catch (Exception e) {
   throw ExceptionUtil.unwrapThrowable(e);
  }
 }
 
 private static Map, Set> getSignatureMap(Interceptor interceptor) {
  Intercepts interceptsAnnotation = interceptor.getClass().getAnnotation(Intercepts.class);
  if (interceptsAnnotation == null) { // issue #251
   throw new PluginException("No @Intercepts annotation was found in interceptor " + interceptor.getClass().getName());   
  }
  Signature[] sigs = interceptsAnnotation.value();
  Map, Set> signatureMap = new HashMap, Set>();
  for (Signature sig : sigs) {
   Set methods = signatureMap.get(sig.type());
   if (methods == null) {
    methods = new HashSet();
    signatureMap.put(sig.type(), methods);
   }
   try {
    Method 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() + ". Cause: " + e, e);
   }
  }
  return signatureMap;
 }
 
 private static Class[] getAllInterfaces(Class type, Map, Set> signatureMap) {
  Set> interfaces = new HashSet>();
  while (type != null) {
   for (Class c : type.getInterfaces()) {
    if (signatureMap.containsKey(c)) {
     interfaces.add(c);
    }
   }
   type = type.getSuperclass();
  }
  return interfaces.toArray(new Class[interfaces.size()]);
 }
 
}

The setProperties method, as its name implies, is used to set the. bean has many methods of property initialization, and this is one of them.

mybatis provides the @ Intercepts annotation to declare that the current class is an interceptor, with a value of @ Signature array indicating the interface, method, and corresponding parameter type to be intercepted


@Intercepts({@Signature(method = "prepare", type = StatementHandler.class, args = {Connection.class}),
    @Signature(method = "query", type = StatementHandler.class, args = {java.sql.Statement.class, ResultHandler.class})})
public class TenantInterceptor implements Interceptor {
.....

For example, in the class declaration above, the first Signature annotation intercepts a method named prepare whose entry parameter under the StatementHandler class is an Connection.

The second Signature annotation intercepts a method named query that contains two input parameters (Statement and ResultHandler types, respectively) in the StatementHandler class.

Finally, the declared Interceptor needs to be registered with plug of mybatis to take effect.


  <!--  Configure mybatis -->
  <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <property name="dataSource" ref="dataSource"/>
    <property name="configLocation" value="classpath:mybatis/mybatis-config.xml"/>
    <!-- mapper Scanning  -->
    <property name="mapperLocations" value="classpath:mybatis/*/*.xml"/>
    <property name="plugins">
      <array>
        <!--  Register your own interceptor  -->
        <bean id="paginationInterceptor" class="xxx.xxx.TenantInterceptor">
        </bean>
      </array>
    </property>
  </bean>

Related articles: