zoukankan      html  css  js  c++  java
  • Mybatis: 插件及分页

    Mybatis采用责任链模式,通过动态代理组织多个拦截器(插件),通过这些拦截器可以改变Mybatis的默认行为(诸如SQL重写之类的)。

    Mybatis支持对ExecutorStatementHandlerParameterHandlerResultSetHandler进行拦截。

    插件的运行时的逻辑:

    1. 所有可能被拦截的处理类都会生成一个代理。
    2. 代理类在执行对应方法时,判断要不要执行插件中的拦截方法。
    3. 执行插件中的拦截方法后,推进目标的执行。

    Executor

    前面博客介绍过Executor是在openSession()的过程中被创建的。在调用ConfigurationnewExecutor()方法创建Executor时会进行以下操作。

    1
    executor = (Executor) interceptorChain.pluginAll(executor);

    每一个拦截器对目标类都进行一次代理,层层进行。

    executor执行了多次plugin,第一次plugin后通过Plugin.wrap方法生成了第一个代理类,姑且就叫executorProxy1,这个代理类的target属性是该executor对象。第二次plugin后通过Plugin.wrap方法生成了第二个代理类,姑且叫executorProxy2,这个代理类的target属性是executorProxy1…这样通过每个代理类的target属性就构成了一个代理链。

    InterceptorChain
    1
    2
    3
    4
    5
    6
    public Object (Object target) {
    for (Interceptor interceptor : interceptors) {
    target = interceptor.plugin(target);
    }
    return target;
    }

    Interceptor

    拦截器Interceptor接口定义了三个方法:

    • intercept(): 内部要通过invocation.proceed()显式地推进责任链前进,也就是调用下一个拦截器拦截目标方法。
    • plugin(): 用当前这个拦截器生成对目标target的代理,实际是通过Plugin.wrap(target,this) 来完成的,把目标target和拦截器this传给了包装函数。
    • setProperties(): 用于设置额外的参数,参数配置在拦截器的Properties节点里。
    自定义拦截器
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    package plugin;
    import org.apache.ibatis.executor.Executor;
    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.util.Properties;
    ({@Signature(
    type= Executor.class,
    method = "query",
    args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class})})
    public class MyPlugin implements Interceptor{
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
    }
    @Override
    public Object plugin(Object target) {
    return Plugin.wrap(target, this);
    }
    @Override
    public void setProperties(Properties properties) {
    }
    }

    Plugin

    生成拦截器代理对象是在Plugin.wrap()中完成的,Plugin本身实现了InvocationHandler(JDK代理实现)。

    • target: 被代理的目标类。
    • interceptor: 对应的拦截器。
    • signatureMap: 拦截器拦截的方法缓存。

    Plugin代理对象的invoke()完成对目标类的方法调用。如果方法签名和拦截中的签名一致,就调用拦截器的拦截方法intercept(),传递的是一个Invocation对象。

    Plugin
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    public class Plugin implements InvocationHandler {
    ...
    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大专栏  Mybatis: 插件及分页pan> Proxy.newProxyInstance(
    type.getClassLoader(),
    interfaces,
    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)) {
    return interceptor.intercept(new Invocation(target, method, args));
    }
    return method.invoke(target, args);
    } catch (Exception e) {
    throw ExceptionUtil.unwrapThrowable(e);
    }
    }
    ...
    }

    Invocation

    Invocation对象保存了代理对象的目标类,执行的目标类方法以及传递给它的参数,真正执行目标类的方法调用是在Invocation中的proceed()方法。
    所以代理对象的invoke()中调用拦截器的intercept(Invocation invocation)方法后,在该方法还必须调用invocation.proceed()方法才能使代理链继续执行下去

    Invocation
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    package org.apache.ibatis.plugin;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    public class Invocation {
    private Object target;
    private Method method;
    private Object[] args;
    ...
    public Object proceed() throws InvocationTargetException, IllegalAccessException {
    return method.invoke(target, args);
    }
    }

    详情参见:Mybatis 插件原理

    分页

    Mybatis的分页功能是基于内存的分页,也就是查出所有记录再按照偏移量offset和limit取出结果。在大数据量的情况下不适用,下面两个插件都是通过拦截Executor等重写sql语句实现数据库的物理分页。

    逻辑分页(内存分页)

    Mybatis 自身通过 RowBounds来完成内存分页的。

    Test
    1
    2
    3
    4
    List<Actor> actorsInMem = mapper.selectAll(new RowBounds(10, 5));
    for (Actor actor: actorsInMem){
    System.out.println(actor);
    }

    ActorMapper
    1
    List<Actor> selectAll(RowBounds rowBounds);
    ActorMapper.xml
    1
    2
    3
    4
    5
    <select id="selectAll" resultMap="BaseResultMap">
    select
    <include refid="Base_Column_List"/>
    from actor
    </select>
    输出结果(第3页数据)
    1
    2
    3
    4
    5
    Actor{actorId=11, firstName='ZERO', lastName='CAGE', lastUpdate=Wed Feb 15 04:34:33 CST 2006}
    Actor{actorId=12, firstName='KARL', lastName='BERRY', lastUpdate=Wed Feb 15 04:34:33 CST 2006}
    Actor{actorId=13, firstName='UMA', lastName='WOOD', lastUpdate=Wed Feb 15 04:34:33 CST 2006}
    Actor{actorId=14, firstName='VIVIEN', lastName='BERGEN', lastUpdate=Wed Feb 15 04:34:33 CST 2006}
    Actor{actorId=15, firstName='CUBA', lastName='OLIVIER', lastUpdate=Wed Feb 15 04:34:33 CST 2006}

    物理分页

    物理分页就是在SQL查询过程中实现分页,不同的数据库厂商,实现也会不同。MySql通过在sql语句中添加offset和limit实现。

    分页插件实现,通过添加拦截器,对Executor进行拦截,然后重写sql:

    详情参见:Mybatis 分页

  • 相关阅读:
    Solution SQL Server 2008 Configuration Manager Cannot open problem.
    Grant, Revoke, Deny
    Solution: 无法察看SQL Server 数据库属性 错误: &quot;There is no row at position 0. (System.Data)&quot;
    SQL SMO 学习
    恢复误删的桌面 回收站
    SQL Server Security Understanding.
    SQL Server 中的 Schema, User, Login, Role
    形而上学 辩证法
    文档归档的配置发送
    无法从服务器加载工作流操作
  • 原文地址:https://www.cnblogs.com/lijianming180/p/12247903.html
Copyright © 2011-2022 走看看