Mybatis源碼筆記之淺析StatementHandler

概述

職責

首先了解一下statementHandler職責:主要負責處理MyBatis與JDBC之間Statement的交互,通俗而言就是負責操作Statement對象與數據庫之間的交互。其執行過程中主要依賴ParameterHandler和ResultSetHandler進行參數綁定和結果實體類綁定。

類圖

在這裏插入圖片描述

  • BaseStatementHandler:StatementHandler接口的抽象實現類,主要用於簡化StatementHandler接口的實現難度,適配
    器設計模式的體現,主要有三個實現類:
  1. SimpleStatementHandler:管理Statement對象並向數據庫推送不需要預編譯的SQL語句;
  2. PreparedStatementHandler:管理Statement對象並向數據庫推送需要預編譯的SQL語句;
  3. CallableStatementHandler:管理Statement對象並調用數據庫中的存儲過程;
  • RoutingStatementHandler:StatementHandler接口的另一個實現類,並沒有對Statement對象其實際執行作用,只是根據StatementType來創建一個代理,代理的主要對象即對應BaseStatementHandler的三種實現類。
    從源碼層面理解RoutingStatementHandler:
 public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }
  }

源碼

下面主要以查詢爲例,debugger源碼層面分析。SQL執行開始皆從SqlSession開始。

  • DefaultSqlSession
    該類中針對增刪改查存在多個重載方法,以selectList爲例;
@Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      //調用Executor中的query
      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();
    }
  }
  • BaseExecutor
 /**
     * 查詢方法,專門提供select執行的方法
     */
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
        //獲取查詢SQL
        BoundSql boundSql = ms.getBoundSql(parameter);
        //創建緩存的key,即作爲HashMap中的key
        CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
        //執行查詢
        return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
    }

此段代碼塊中涉及到BoundSQL對象,此處簡單提一下相關概念,後期有時間專門總結一下BoundSQl.
BoundSQl對象主要是用於存儲SQL語句,以及對應的參數相關對象。
繼續調用BaseExcutor中的重載query方法:

 /**
     * 執行查詢邏輯,
     * 首先從緩存中獲取數據,緩存中有數據則進行處理存儲過程;
     * 如果緩存中沒有數據,則交互數據庫查詢數據,則將查詢結果添加到緩存中
     */
    @SuppressWarnings("unchecked")
    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        //如果不是嵌套查詢,且動態查詢語句中flushCache = true時即<select id= "xx" flushCache = true>纔會清空緩存
        if (queryStack == 0 && ms.isFlushCacheRequired()) {
            clearLocalCache();
        }
        List<E> list;
        try {
            //嵌套查詢層數+1
            queryStack++;
            //首先從一級緩存中進行查詢:根據key獲取對象
            list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
            if (list != null) {
                //從緩存中獲取對象,則進行處理存儲過程
                handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
            } else {
                //否則即緩存中沒有對應的數據,則交互數據庫從數據庫中查詢數據
                list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
            }
        } finally {
            queryStack--;
        }
        if (queryStack == 0) {
            for (DeferredLoad deferredLoad : deferredLoads) {
                deferredLoad.load();
            }
            // issue #601
            deferredLoads.clear();
            if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
                // issue #482
                clearLocalCache();
            }
        }
        return list;
    }

繼續跟進queryFromDatabase方法,該方法主要從數據庫中查詢數據

/**
     * 交互數據庫從數據庫中查詢數據,再把查詢結果添加到緩存中
     */
    private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
        List<E> list;
        //向緩存中添加佔位符,此時緩存中沒有真正所需要的查詢數據
        localCache.putObject(key, EXECUTION_PLACEHOLDER);
        try {
            //查詢數據庫,由其子類實現,獲取對應的查詢數據
            list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
        } finally {
            //刪除佔位符
            localCache.removeObject(key);
        }
        //再將從數據庫查詢的結果添加到一級緩存中
        localCache.putObject(key, list);
        //處理存儲過程
        if (ms.getStatementType() == StatementType.CALLABLE) {
            localOutputParameterCache.putObject(key, parameter);
        }
        return list;
    }

繼續跟進doQuery方法,發現BaseExecutor抽象類中該方法並沒有實體,僅提供一個鉤子方法,而是交給其子類實現,這裏體現了模板設計模式。

 /**交互數據庫,查詢數據*/
    protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
            throws SQLException;

StatementHandler對象創建

  • SimpleExcutor
    從BaseExcutor類中跟進其子類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();
      //Configuration中獲取StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

到目前爲止,我們發現了StatementHandler對象的來源自Configuration中newStatementHandler方法創建;
驚喜若現,繼續跟進去;

  • configuration
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//創建StatementHandler
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

最終,我們可以明確地發現StatementHandler對象是由其子類RoutingStatementHandler創建的,那麼它創建的具體邏輯又是如何的呢?真相即將浮出水面,我們跟進RoutingStatementHandler的構造函數;

  • RoutingStatementHandler
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

看到一段很熟悉的代碼,我們在文章開頭已經將其展現出來了。
根據statementType的類型來判斷是哪一種StatementHandler的實現,並且RoutingStatementHandler維護了一個delegate對象,通過delegate對象來實現對實際Handler對象的調用。這裏涉及到了一個對象MappedStatement。

  • SimpleExecutor
    衆所周知,Excutor是主要負責執行對數據庫的操作主要執行者,經歷上面分析StatementHandler對象的創建過程,下面繼續迴歸到SimpleExectuor中;
    獲取到StatementHandler之後,首先進入prepareStatement方法,該方法就是爲了獲取Statement對象,並設置Statement對象中的參數:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    Connection connection = getConnection(statementLog);
    stmt = handler.prepare(connection, transaction.getTimeout());
    handler.parameterize(stmt);
    return stmt;
  }
  • BaseStatementHandler
    prepare方法負責生成Statement實例對象
@Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    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);
    }
  }
  • prepareStatementHandler
    parameterize方法用於處理Statement實例對應的參數。此處我們跟進下去,便可以瞭解ParameterHandler是如何解析參數的過程。
@Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }
  • ParameterHandler
    此接口只有一個默認實現類DefaultParameterHandler,跟進setParameters方法
@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // parameterMappings 就是對 #{} 或者 ${} 裏面參數的封裝
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      // 如果是參數化的SQL,便需要循環取出並設置參數的值
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 如果參數類型不是 OUT ,這個類型與 CallableStatementHandler 有關
        // 因爲存儲過程不存在輸出參數,所以參數不是輸出參數的時候,就需要設置。
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          // 得到#{}  中的屬性名
          String propertyName = parameterMapping.getProperty();
          // 如果 propertyName 是 Map 中的key
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            // 通過key 來得到 additionalParameter 中的value值
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            // 如果不是 additionalParameters 中的key,而且傳入參數是 null, 則value 就是null
            value = null;
          }
          // 如果 typeHandlerRegistry 中已經註冊了這個參數的 Class對象,即它是Primitive 或者是String 的話
          else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            // 否則就是 Map
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          // 在通過SqlSource 的parse 方法得到parameterMappings 的具體實現中,我們會得到parameterMappings的typeHandler
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          // 獲取typeHandler 的jdbc type
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          } catch (SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }
  • SampleExecutor
    執行完成參數的解析,繼續迴歸到SampleExecutor#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();
      //Configuration中獲取StatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

後面再次調用query方法,即SampleStatementHandler,PrepareStatementHandler,CallableStatementHandler中方法執行結果實體類綁定,這個具體過程解析,後面計劃專門有文章總結。

總結

經歷上面的跟蹤源碼,我們可以瞭解到StatementHandler對象具體的創建過程,以及參數和結果綁定的流程。

新手跟蹤源碼,若存在錯誤或者不足之處,希望大佬及時指正!最後,希望大家多多支持,轉發,點贊,關注,謝謝。

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