Mybatis源码分析 分页插件原理

1,448 阅读2分钟

在Mybatis中,我们经常会遇到分页的问题,但是很少有人会去手动写分页的sql语句,一般都是使用pageHelper或者MybatisPlus等第三方插件,那么究竟底层mybatis是如何做的呢,看下源码就知道。

首先可以想到的是,既然需要帮助开发人员进行分页,那么就需要有拦截器才行,那在哪里拦截好呢,肯定是执行sql的地方了,Mybatis底层也是jdbc,所以既然是jdbc,那就有获取sql,执行sql的地方了。我们只需要在执行sql的地方把sql语句拦截到,在原sql基础上修改就可以了。

说下拦截器:

// 一个拦截器框架  
// type  拦截的地方
// method 拦截的方法,参数为
// args  方法的参数  确定是哪一个方法
@Intercepts({@Signature(type=Executor.class,method="query",args={ MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class })})  
public class PageInterceptor implements Interceptor{  
    // 执行的拦截逻辑
  public Object intercept(Invocation invocation) throws Throwable {  
    return invocation.proceed();  
  }  
  // 判断验证是否是需要拦截的
  public Object plugin(Object arg0) {  
     return Plugin.wrap(arg0, this);  
  }  
  // 拦截器传的参数
  public void setProperties(Properties arg0) {  
  }  
}  

mybatis配置文件中配置拦截器
<plugins>  
   <plugin interceptor="cn.com.dingding.common.utils.PageInterceptor">    
   </plugin>  
</plugins>    

下面看下原理

// 这是加载过程

会在parseConfiguration中解析配置文件中的plugin节点
// 解析 plugins 节点
pluginElement(root.evalNode("plugins"));
  
  
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);
            // 放入配置类的interceptor中
            configuration.addInterceptor(interceptorInstance);
        }
    }
}  

// 构建过程 这里只看执行器的 configuration类中,搜索一下interceptorChain.pluginAll,就能找到其他的

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    // ....省略  看重点
    // 创建Executor代理对象
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }
  

// 加载配置的时候set的值
private final List<Interceptor> interceptors = new ArrayList<>();
// 循环处理拦截器链
public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
        // 不断的对executor进行代理
        // 此时就会调用刚刚开始的例子处的plugin方法了,因为那个方法实现了Interceptor接口
        target = interceptor.plugin(target);
    }
    return target;
}

// 
@Override
public Object plugin(Object target) {
    // 对target进行包装
    return Plugin.wrap(target, this);
}


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,
            // 执行Plugin类的invoke方法
            new Plugin(target, interceptor, signatureMap));
    }
    return target;
}
// 看下具体执行的逻辑
@Override
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)) {
        // 此处调用了自定义拦截器的interceptor
        return interceptor.intercept(new Invocation(target, method, args));
    }
        return method.invoke(target, args);
    } catch (Exception e) {
        throw ExceptionUtil.unwrapThrowable(e);
    }
}


@Override
public Object intercept(Invocation invocation) throws Throwable {
    // ...
    //  反射调用拦截器
    return invocation.proceed();
}


public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
}