天天看點

mybatis的解析和運作原理(四)前言:SqlSession下的四大對象總結:

前言:

之前的SqlSession的運作過程還未講完,還差一個SqlSession下的最重要的四大對象,這一篇文章将會講解四大對象之間的關系.

SqlSession下的四大對象

之前講解了映射器其實就是一個動态代理的對象,進入到MapperMethod的execute方法,那麼進入到execute的方法又是如何執行的呢?肯定是通過我們所寫的Sql語句,但是我們關心的不是這些細節,而是架構的設計這些,現在我們來分析這些對象.

1.Executor 執行器,用于排程其他對象

2.StatementHandler 調用statement執行操作

3.ParameterHandler 處理Sql參數

4.ResultHandler 處理最後的結果集封裝并傳回

一.Executor

用于與資料庫進行互動,為我們提供了查詢,更新,事務等方法,這和其他架構并無不同,那麼先讓我們看看Executor如何建立的把

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
        executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
        executor = new ReuseExecutor(this, transaction);
    } else {
        executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
        executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
}
           

他會根據你選擇的三種執行器的哪一種去建立一個對象,建立對象後回去執行最後這句(Executor) interceptorChain.pluginAll(executor),其實這句就是mybatis的插件,它為我們建構了動态代理對象,然後在Executor執行之前先配置mybatis插件.

然後我們再從他的一個BaseExecutor中的updata再來進行分析

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
    StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    return handler.update(stmt);
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    BoundSql boundSql = handler.getBoundSql();
    String sql = boundSql.getSql();
    if (hasStatementFor(sql)) {
        stmt = getStatement(sql);
        applyTransactionTimeout(stmt);
    } else {
        Connection connection = getConnection(statementLog);
        stmt = handler.prepare(connection, transaction.getTimeout());
        putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
}
           

上面内容很顯然Mybatis根據Configuration來建構StatementHandler,然後使用prepareStatement對SQL語句進行編譯,它先調用了StatementHandler的prepare()方法進行預編譯,然後通過StatementHandler的parameterize()方法來設定參數,resultHandler組裝結果并傳回結果,看到上面這段代碼,我們的注意力就要從Executor中出來了,那麼再讓我們去分析一下StatementHandler這個對象吧.

二.StatementHandler

StatementHandler專門用來處理資料庫之間的會話類,同樣的我們來先看看Mybatis如何建立這個類的把.

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

這麼幾句話很顯然建立的真實對象其實是RoutingStatementHandler隻是實作了StatementHandler這個接口,那麼它和Executor一樣,都隻是用代理對象進行了一步步的封裝,他和Executor一樣為我們提供了三種執行器分别時SimpleStatementHandler,PreparedStatementHandler,

CallableStatementHandler,他會根據上下文決定建立那個執行器

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());
    }

  }
           

這是RouingStatementHandler類下的構造方法,它提供了一個是配置delegate,他的作用是給實作類對象的使用提供一個統一,簡易的擴充卡.再來看看他的主要三個方法之一的prepare,之後再按照順序執行parameterize,query

public Statement prepare(Connection connection) throws SQLException {
    ErrorContext.instance().sql(boundSql.getSql());
    Statement statement = null;
    try {
      statement = instantiateStatement(connection);
      setStatementTimeout(statement);
      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);
    }
  }
           

以上代碼中的instantiateStatement方法是對SQL進行了預編譯和基礎配置然後調用parameterize方法去設定參數

public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }
           

繼續檢視query方法

public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {  
    PreparedStatement ps = (PreparedStatement) statement; 
    ps.execute();
    return resultSetHandler.<E> handleResultSets(ps);  
  }  
           

現在就非常明确了,在得到DefaultSqlSession對象時初始化了Executor執行器。然後在調用Executor的doQuery方法查詢的時候,會初始化StatementHandler對象,然後在初始化StatementHandler的過程中,會初始化ParameterHandler和ResultSetHandler對象,生成的StatementHandler對象會儲存這兩個對象的引用。直到這時,Executor的職責就差不多完成了,通過其他的三大元件來完成接下來的資料庫查詢工作并傳回結果。

三.ParameterHandler

public interface ParameterHandler {
  Object getParameterObject();
  void setParameters(PreparedStatement ps)
      throws SQLException;
}
           

getParameterObject方法作用是傳回參數對象,setParameter設定預編譯SQL語句的參數.

那麼我們來分析一下setParameter的實作吧.

@Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
        ParameterMapping parameterMapping = parameterMappings.get(i);
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          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);
          }
        }
      }
    }
  }
}
           

從parameterObject中取參數,根據typeHandler進行參數處理.typeHandler在Mybatis初始化的時候,就已經注冊在了Configuration中.

四.ResultHandler

接口代碼我就不貼出來了,根據前面的描述它隻是對最終的結果進行了封裝處理,我就不在這裡多廢話了.

總結:

SqlSession是通過Executor建立StatementHandler來運作,二StatementHandler要進過下面三步:

1.prepared 預編譯

2.parameterize 設定參數

3. query 執行SQL.

以上就是SqlSession的執行過程,明白了四大對象是如何協作的了.

繼續閱讀