天天看點

Mybatis源碼分析(6)—— 從JDBC看Mybatis的設計

Java資料庫連接配接,(Java Database Connectivity,簡稱JDBC)是Java語言中用來規範用戶端程式如何來通路資料庫的應用程式接口,提供了諸如查詢和更新資料庫中資料的方法。

六步流程:

  • 加載驅動(5.x驅動包不需要這步了)
  • 建立連接配接
  • 建立Statement
  • 執行SQL語句
  • 擷取結果集
  • 關閉資源

這裡隻取後面幾步分析下,基本上都是從Executor開始。DefaultSqlSession被每個Mapper持有,将各種基于SQL的操作轉移到調用Executor的query和update方法。

Executor的類圖如下:

Mybatis源碼分析(6)—— 從JDBC看Mybatis的設計

以ReuseExecutor的doQuery方法為例:

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

建立連接配接

上面ReuseExecutor的doQuery方法調用其prepareStatement方法:

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);
    } else {
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection);
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
}
           

然後調用getConnection方法擷取連接配接:

protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled() || connectionLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog);
    } else {
      return connection;
    }
}
           

這個連接配接是從Transaction對象中擷取的。

當我們和Spring一起使用時,當然由Spring統一管理,回顧DefaultSqlSessionFactory類中的openSessionFromDataSource方法,這個連接配接就是找Spring要的(DataSourceUtils#getConnection),要過來放到了SpringManagedTransaction中。

建立Statement

  • Statement createStatement()

建立Statement 對象,Statement接口提供基本執行SQL語句的能力。

  • PreparedStatement prepareStatement(String sql)

建立PreparedStatement對象,PreparedStatement接口繼承了Statement接口,提供SQL語句接受輸入參數的能力。

  • CallableStatement prepareCall(String sql)

建立CallableStatement對象,CallableStatement接口繼承了PreparedStatement接口,提供執行存儲過程的能力。

針對這三種情況,Mybatis提供了三個StatementHandler實作:

Mybatis源碼分析(6)—— 從JDBC看Mybatis的設計

并建立了一個RoutingStatementHandler作為路由(代理類),根據MappedStatement中的statementType來确定建立哪個作為實際的StatementHandler實作。對外提供一緻接口,實際調用交給委托類。

還是接着看上面的prepareStatement方法,它擷取連接配接後就接着調用StatementHandler的prepare方法:

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);
    } else {
      Connection connection = getConnection(statementLog);
      stmt = handler.prepare(connection);
      putStatement(sql, stmt);
    }
    handler.parameterize(stmt);
    return stmt;
}
           

這個來自BaseStatementHandler的prepare方法又是調用子類的instantiateStatement方法來執行個體化Statement:

protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() != null) {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    } else {
      return connection.prepareStatement(sql);
    }
}
           

這裡最終調用connection的prepareStatement方法,回到了我們傳統的操作。

執行SQL語句

在Executor的實作類中,doUpdate和doQuery方法都會調用下面兩個方法:

handler.update(stmt);

handler.query(stmt, resultHandler);

SimpleStatementHandler的query方法:

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

PreparedStatementHandler的query方法:

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

CallableStatementHandler的query方法:

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

這裡都是直接調的Statement或PreparedStatement的execute方法。

傳回結果集

上面的三種StatementHandler,最終都會處理結果集,CallableStatementHandler有點特殊,其它兩種都是調用ResultSetHandler的handleResultSets方法來處理結果集。ResultSetHandler在基類BaseStatementHandler的構造函數中通過configuration對象來建立:

this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);

具體的實作有兩種,FastResultSetHandler和NestedResultSetHandler,且NestedResultSetHandler繼承了FastResultSetHandler,重寫了handleRowValues方法。

在FastResultSetHandler的handleResultSets方法中:

public List<Object> handleResultSets(Statement stmt) throws SQLException {
    final List<Object> multipleResults = new ArrayList<Object>();
    final List<ResultMap> resultMaps = mappedStatement.getResultMaps();
    int resultMapCount = resultMaps.size();
    int resultSetCount = ;
    ResultSet rs = stmt.getResultSet();

    while (rs == null) {
      // move forward to get the first resultset in case the driver
      // doesn't return the resultset as the first result (HSQLDB 2.1)
      if (stmt.getMoreResults()) {
        rs = stmt.getResultSet();
      } else {
        if (stmt.getUpdateCount() == -) {
          // no more results.  Must be no resultset
          break;
        }
      }
    }
    // 驗證是否定義了resultType或者resultMap
    validateResultMapsCount(rs, resultMapCount);
    while (rs != null && resultMapCount > resultSetCount) {
      final ResultMap resultMap = resultMaps.get(resultSetCount);
      ResultColumnCache resultColumnCache = new ResultColumnCache(rs.getMetaData(), configuration);
      handleResultSet(rs, resultMap, multipleResults, resultColumnCache);
      rs = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
    return collapseSingleResultList(multipleResults);
}
           

處理結果行:

protected void handleRowValues(ResultSet rs, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultColumnCache resultColumnCache) throws SQLException {
    final DefaultResultContext resultContext = new DefaultResultContext();
    skipRows(rs, rowBounds);
    while (shouldProcessMoreRows(rs, resultContext, rowBounds)) {
      final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rs, resultMap, null);
      Object rowValue = getRowValue(rs, discriminatedResultMap, null, resultColumnCache);
      callResultHandler(resultHandler, resultContext, rowValue);
    }
}
           

skipRows就是跳過行,基于查詢結果的分頁使用,其中會直接調用ResultSet的absolute方法:

rs.absolute(rowBounds.getOffset());

而shouldProcessMoreRows方法就是判斷是否應該擷取更多的行:

protected boolean shouldProcessMoreRows(ResultSet rs, ResultContext context, RowBounds rowBounds) throws SQLException {
    return !context.isStopped() && rs.next() && context.getResultCount() < rowBounds.getLimit();
}
           

也對應了分頁的limit字段,這種分頁可以說是僞分頁,查出來再分頁。是以我們一般使用插件的形式來實作分頁,基于sql的動态替換。

具體針對行的處理在getRowValue方法中:

protected Object getRowValue(ResultSet rs, ResultMap resultMap, CacheKey rowKey, ResultColumnCache resultColumnCache) throws SQLException {
    final ResultLoaderMap lazyLoader = instantiateResultLoaderMap();
    Object resultObject = createResultObject(rs, resultMap, lazyLoader, null, resultColumnCache);
    if (resultObject != null && !typeHandlerRegistry.hasTypeHandler(resultMap.getType())) {
      final MetaObject metaObject = configuration.newMetaObject(resultObject);
      boolean foundValues = resultMap.getConstructorResultMappings().size() > ;
      if (shouldApplyAutomaticMappings(resultMap, !AutoMappingBehavior.NONE.equals(configuration.getAutoMappingBehavior()))) {
        final List<String> unmappedColumnNames = resultColumnCache.getUnmappedColumnNames(resultMap, null);
        foundValues = applyAutomaticMappings(rs, unmappedColumnNames, metaObject, null, resultColumnCache) || foundValues;
      }
      final List<String> mappedColumnNames = resultColumnCache.getMappedColumnNames(resultMap, null);
      foundValues = applyPropertyMappings(rs, resultMap, mappedColumnNames, metaObject, lazyLoader, null) || foundValues;
      foundValues = (lazyLoader != null && lazyLoader.size() > ) || foundValues;
      resultObject = foundValues ? resultObject : null;
      return resultObject;
    }
    return resultObject;
}
           

根據ResultMap提供的相關參數(傳回的類型等),構造一個空的對象,然後根據屬性名到ResultSet中擷取結果,再通過MetaObject給set進對象裡。

具體的取值是通過TypeHandler:

final Object value = typeHandler.getResult(rs, columnName);

每種類型的都對應一個TypeHandler的實作,以Integer類型的為例(IntegerTypeHandler):

@Override
public Integer getNullableResult(ResultSet rs, String columnName)
  throws SQLException {
    return rs.getInt(columnName);
}
           

架構就是這樣,底層就是最基本的原理和最簡單的接口,而這些接口通常就是所謂的标準。它所做的事就是解放我們的雙手,把能省的都給省了,讓我們專注于業務代碼的編寫。

但我們也應該知其是以然。一是出于好奇心,這是程式員的本能;二是為了學習其優秀的思想和設計理念,以後可以為我所用,這可稱之為野心吧。

繼續閱讀