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 查詢緩存結果

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章