spring-mybatis-4(mybatis插件)

Mybatis拦截器

拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,
也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。

Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。
打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。
这个时候如果你觉得这几种实现对于Executor接口的query方法都不能满足你的要求,那怎么办呢?是要去改源码吗?
当然不。我们可以建立一个Mybatis拦截器用于拦截Executor接口的query方法,在拦截之后实现自己的query方法逻辑,
之后可以选择是否继续执行原来的query方法。

对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。

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

Mybatis拦截器只能拦截四种类型的接口:

  • Executor
  • StatementHandler
  • ParameterHandler
  • ResultSetHandler

Executor拦截器的执行顺序,其他接口拦截器类似
Executor.Method->Plugin.invoke->Interceptor.intercept->Invocation.proceed->method.invoke
在这里插入图片描述

Mybatis一级缓存
PerpetualCache
基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。默认就支持一级缓存的

mybatis 拦截器定义

org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);      
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 查看该方法 newExecutor,返回的是代理对象
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); 
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)

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

org.apache.ibatis.plugin.InterceptorChain#pluginAll

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
     // 查看自定义插架中的方法
      target = interceptor.plugin(target);
    }
    return target;
  }

com.dn.spring.mybatis.interceptor.ExectorInterceptor#plugin
自定义 插件中必须做类型判断

@Override
    public Object plugin(Object target) {
        //必须判断是否是拦截的类型
        if (target instanceof Executor) {
            //生成代理类
            return Plugin.wrap(target, this);
        }
        return target;
    }

mybatis拦截器定义

import org.apache.ibatis.plugin.*;


@Intercepts(
        //每配置一个 @Signature 则就可以拦截一个方法
        // 这里指要拦截 Executor 类中的 query 方法
        {@Signature(method = "query", type = Executor.class,
                //根据参数的类型找对应的方法
                args = {
                        MappedStatement.class, Object.class, RowBounds.class,
                        ResultHandler.class})
        }
)
public class ExectorInterceptor implements Interceptor {

}

org.apache.ibatis.plugin.Plugin#wrap

public static Object wrap(Object target, Interceptor interceptor) {
    //@Intercepts 注解解析
    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;
  }

查看
org.apache.ibatis.session.defaults.DefaultSqlSession#selectList(java.lang.String, java.lang.Object, org.apache.ibatis.session.RowBounds)

@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      // executor 是代理对象,则会调入到 org.apache.ibatis.plugin.Plugin#invoke
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

插件会生成代理类,所以调用代理类方法时,会调入到 Plugin#invoke
org.apache.ibatis.plugin.Plugin#invoke

@Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
    // 获取需要拦截的方法,也就是插件上定义的方法,例如:插件上定义的 method = "query"
      Set<Method> methods = signatureMap.get(method.getDeclaringClass());
      if (methods != null && methods.contains(method)) {
        // 调用自定义插件中的 intercept 方法
        return interceptor.intercept(new Invocation(target, method, args));
      }
      //调用被代理的方法
      return method.invoke(target, args);
    } catch (Exception e) {
      throw ExceptionUtil.unwrapThrowable(e);
    }
  }

见:com.dn.spring.mybatis.interceptor.ExectorInterceptor 插件的定义

查看源码
org.apache.ibatis.executor.SimpleExecutor#doQuery

 @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

见 com.dn.spring.mybatis.interceptor.PageInterceptor 插件,拦截 StatementHandler 类

org.apache.ibatis.plugin.InterceptorChain#pluginAll

  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      //循环插件,调用自定义插件中的 plugin 方法
      target = interceptor.plugin(target);
    }
    return target;
  }

com.dn.spring.mybatis.interceptor.PageInterceptor#plugin
这里需要在插件中需要判断调用的目标对象是不是需要处理的类,必须要判断的,不判断时,返回的对象就错了

    @Override
    public Object plugin(Object target) {
        if (target instanceof RoutingStatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

分页插件定义
org.apache.ibatis.executor.SimpleExecutor#doQuery

查看该方法
stmt = prepareStatement(handler, ms.getStatementLog());

org.apache.ibatis.executor.SimpleExecutor#prepareStatement

  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    // 对 prepare 方法进行拦截
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }

org.apache.ibatis.executor.statement.BaseStatementHandler#prepare

@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    // 分页时,需要更改 sql 的语句
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement, transactionTimeout);
      setFetchSize(statement);
      return statement;
    } catch (SQLException e) {
      closeStatement(statement);
      throw e;
    } catch (Exception e) {
      closeStatement(statement);
      throw new ExecutorException("Error preparing statement.  Cause: " + e, e);
    }
  }

见分页插件 com.dn.spring.mybatis.interceptor.PageInterceptor
分页逻辑

  1. 拿到被代理对象RoutingStatementHandler
  2. 拿到被代理对象的private final StatementHandler delegate; PreparedStatementHandler
  3. 拿到PreparedStatementHandler里面的BoundSql属性
  4. 修改BoundSql属性

对查询结果进行缓存 mybatis插件定义
org.apache.ibatis.executor.SimpleExecutor#doQuery

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
       //执行查询
      return handler.<E>query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

org.apache.ibatis.executor.statement.SimpleStatementHandler#query

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    String sql = boundSql.getSql();
    statement.execute(sql);
    //查询结果返回值:handleResultSets 处理
    return resultSetHandler.<E>handleResultSets(statement);
  }

见:com.dn.spring.mybatis.interceptor.ResultSetCacheInterceptor 缓存查询结果
见:com.dn.spring.mybatis.interceptor.ExectorInterceptor 查询缓存结果

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值