MyBatis plug-in principle --- paging plug-in PageHelper

Instructions

1. Add Maven dependency

<dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.0.0</version>
</dependency>

2. Configure PageHelper plug-in parameters in Mybatis-Config.xml

<plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 4.0.0以后版本可以不设置该参数 ,可以自动识别
            <property name="dialect" value="mysql"/>  -->
            <!-- 该参数默认为false -->
            <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 -->
            <!-- 和startPage中的pageNum效果一样-->
            <property name="offsetAsPageNum" value="true"/>
            <!-- 该参数默认为false -->
            <!-- 设置为true时,使用RowBounds分页会进行count查询 -->
            <property name="rowBoundsWithCount" value="true"/>
            <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 -->
            <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型)-->
            <property name="pageSizeZero" value="true"/>
            <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 -->
            <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 -->
            <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 -->
            <property name="reasonable" value="true"/>
            <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 -->
            <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 -->
            <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,orderBy,不配置映射的用默认值 -->
            <!-- 不理解该含义的前提下,不要随便复制该配置 -->
            <property name="params" value="pageNum=start;pageSize=limit;"/>
            <!-- 支持通过Mapper接口参数来传递分页参数 -->
            <property name="supportMethodsArguments" value="true"/>
            <!-- always总是返回PageInfo类型,check检查返回类型是否为PageInfo,none返回Page -->
            <property name="returnPageInfo" value="check"/>
        </plugin>
    </plugins>

Note: The configuration of plugins is after settings and before environments, otherwise the configuration file cannot be parsed

3. Code

		PageHelper.startPage(pn, 10); //pageNumber, pageSize,第几页,每页几条
        List<Employee> emps = employeeService.getAll();
        PageInfo page = new PageInfo(emps, 10);
        return Msg.success().add("pageInfo", page);

principle

As can be seen from the configuration file, the core class of the pageHelper paging plug-in is com.github.pagehelper.PageInterceptor

package com.github.pagehelper;

import com.github.pagehelper.cache.Cache;
import com.github.pagehelper.cache.CacheFactory;
import com.github.pagehelper.util.MSUtils;
import com.github.pagehelper.util.StringUtil;
import org.apache.ibatis.cache.CacheKey;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.apache.ibatis.session.RowBounds;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Properties;

/**
 * Mybatis - 通用分页拦截器<br/>
 * 项目地址 : http://git.oschina.net/free/Mybatis_PageHelper
 *
 * @author liuzh/abel533/isea533
 * @version 5.0.0
 */
@SuppressWarnings({
    
    "rawtypes", "unchecked"})
@Intercepts(
    {
    
    
        @Signature(type = Executor.class, method = "query", args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    }
)
public class PageInterceptor implements Interceptor {
    
    
    //缓存count查询的ms
    protected Cache<CacheKey, MappedStatement> msCountMap = null;
    private Dialect dialect;
    private String default_dialect_class = "com.github.pagehelper.PageHelper";
    private Field additionalParametersField;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    
    
        ...
    }
    @Override
    public Object plugin(Object target) {
    
    
        return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
    
    
       ...
    }

}

It mainly implements the org.apache.ibatis.plugin.Interceptor interface. There are mainly three methods

  • intercept where the intercept content is executed, intercept the execution of the target method of the target object
  • The plugin decides whether to trigger the intercept() method and wraps the target object. The packaging is to create a proxy object for the target object
  • setProperties passes the property parameters of the xml configuration to the custom interceptor. Set the property attribute when the plugin is registered
package org.apache.ibatis.plugin;

import java.util.Properties;

/**
 * @author Clinton Begin
 */
public interface Interceptor {
    
    

  Object intercept(Invocation invocation) throws Throwable;
  
  default Object plugin(Object target) {
    
    
    return Plugin.wrap(target, this);
  }
  
  default void setProperties(Properties properties) {
    
    
  }
}

Also need to pay attention to is the @Intercepts annotation of PageInterceptor, which specifies the intercepted objects and methods. By default, MyBatis allows the use of plug-ins to intercept method calls including:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)
@Intercepts(
    {
    
    
        @Signature(type = Executor.class, method = "query", args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class}),
        @Signature(type = Executor.class, method = "query", args = {
    
    MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}),
    }
)

After understanding the core classes of PageHelper, let's take a look at how it works.

1. Load

First, when myBatis generates the sqlSessionFactory session factory, the plugin node in the mybatis-config.xml configuration file is loaded

XMLConfigBuilder.java

private void pluginElement(XNode parent) throws Exception {
    
    
    if (parent != null) {
    
    
      for (XNode child : parent.getChildren()) {
    
    
        String interceptor = child.getStringAttribute("interceptor");
        Properties properties = child.getChildrenAsProperties();
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).getDeclaredConstructor().newInstance();
        interceptorInstance.setProperties(properties);
        configuration.addInterceptor(interceptorInstance);
      }
    }
  }

Read all interceptor nodes in a loop, and force them into Interceptor objects. Put it into an InterceptorChain container for management

InterceptorChain.java

public class InterceptorChain {
    
    
  private final List<Interceptor> interceptors = new ArrayList<>();
  public void addInterceptor(Interceptor interceptor) {
    
    
    interceptors.add(interceptor);
  }

It can be seen from the class name that mybaits manages all the plug-ins using the chain of responsibility design pattern
. It can be seen through code paging that it has not modified the original code. How does it change and enhance the behavior of the object? In addition, if I have multiple plug-ins that intercept the same class, how does it intercept them layer by layer?
Boldly guess, change and enhance the object through the proxy model; implement layer-by-layer interception through the responsibility chain model.
Now, we enter the code to see if it is the same as we guessed.

2. Intercept

In the @Intercepts annotation, you can see that the object intercepted by the paging plug-in is the executor under Execut, and the executor of mybatis is created in the session sqlSession

Configuration.java

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    
    
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
    
    
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
    
    
      executor = new ReuseExecutor(this, transaction);
    } else {
    
    
      // 默认 SimpleExecutor
      executor = new SimpleExecutor(this, transaction);
    }
    // 二级缓存开关,settings 中的 cacheEnabled 默认是 true
    if (cacheEnabled) {
    
    
      executor = new CachingExecutor(executor);
    }
    // 植入插件的逻辑,至此,四大对象已经全部拦截完毕
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

InterceptorChain.java

public Object pluginAll(Object target) {
    
    
    for (Interceptor interceptor : interceptors) {
    
    
      target = interceptor.plugin(target);
    }
    return target;
  }

As guessed, all plugins are managed using the chain of responsibility model.
According to guess, the interceptor.plugin(target) method is used to create proxy objects, click in and have a look

Interceptor.java

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

Plugin.java

 public static Object wrap(Object target, Interceptor interceptor) {
    
    
    Map<Class<?>, Set<Method>> 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;
  }

Indeed, as guessed, a wrap method is encapsulated through Plugin to generate a jdk dynamic proxy class.
In the Proxy.newProxyInstance method, there are three parameters:

  • ClassLoader loader
  • Class<?>[] interfaces All interface information of the proxy class
  • InvocationHandler h is a processing class that implements the invoke method in the InvocationHandler interface.

So the next thing we need to focus on is the invoke() method in the Plugin class.

 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    
    
    try {
    
    
      //获取被代理类的所有方法
      Set<Method> 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);
    }
  }

In this way, we return to the initial interceptor intercept() method. Here, it encapsulates an Invocation class, which can also be understood as the encapsulation of the proxy class.

public class Invocation {
    
    

  private final Object target;
  private final Method method;
  private final Object[] args;

  public Invocation(Object target, Method method, Object[] args) {
    
    
    this.target = target;
    this.method = method;
    this.args = args;
  }
  public Object proceed() throws InvocationTargetException, IllegalAccessException {
    
    
  	//被代理类本来的方法
    return method.invoke(target, args);
  }
}

In other words, we only need to execute the proceed() method, which is equivalent to executing the original method of the proxy class. In this way, when we write the plug-in class by ourselves, we can call the proceed() method after the processing is complete.

to sum up

Key objects of Mybatis plug-in:

  • Interceptor interface: custom interceptor (implementation class)
  • InterceptorChain: a container for storing plug-ins
  • Plugin: h object, provides a method to create a proxy class
  • Invocation: encapsulation of the proxy class

Work flow chart of the plug-in:

Insert picture description here
When there are multiple plugins:
Insert picture description here

Guess you like

Origin blog.csdn.net/xzw12138/article/details/106281035