Mybatis 源碼(五)Mybatis 中的數據讀寫

數據讀寫的本質

無論是哪一種ORM框架,數據讀寫其本質都是對JDBC的封裝,其目的主要都是簡化JDBC的開發流程,進而讓開發人員更關注業務。下面是JDBC的核心流程:java

  1. 註冊 JDBC 驅動(Class.forName("XXX");)
  2. 打開鏈接(DriverManager.getConnection("url","name","password"))
  3. 根據鏈接,建立 Statement(conn.prepareStatement(sql))
  4. 設置參數(stmt.setString(1, "wyf");)
  5. 執行查詢(stmt.executeQuery();)
  6. 處理結果,結果集映射(resultSet.next())
  7. 關閉資源(finally)

Mybatis也是在對JDBC進行封裝,它將註冊驅動打開鏈接交給了數據庫鏈接池來負責,Mybatis支持第三方數據庫鏈接池,也可使用自帶的數據庫鏈接池;建立 Statement執行查詢交給了StatementHandler來負責;設置參數交給ParameterHandler來負責;最後兩步交給了ResultSetHandler來負責。git

Executor內部運做過程

測試方法org.apache.ibatis.binding.BindingTest#shouldFindThreeSpecificPostsgithub

下面是一個簡單的數據讀寫過程,咱們在宏觀上先來了解一下每個組件在整個數據讀寫上的做用:面試

Mybatis的數據讀寫主要是通Excuter來協調StatementHandler、ParameterHandler和ResultSetHandler三個組件來實現的:sql

  • StatementHandler:它的做用是使用數據庫的Statement或PrepareStatement執行操做,啓承上啓下做用;
  • ParameterHandler:對預編譯的SQL語句進行參數設置,SQL語句中的的佔位符「?」都對應BoundSql.parameterMappings集合中的一個元素,在該對象中記錄了對應的參數名稱以及該參數的相關屬性
  • ResultSetHandler:對數據庫返回的結果集(ResultSet)進行封裝,返回用戶指定的實體類型;

StatementHandler

StatementHandler類圖

RoutingStatementHandler

經過StatementType來建立StatementHandler,使用靜態代理模式來完成方法的調用,主要起到路由做用。它是Excutor組件真正實例化的組件。數據庫

public class RoutingStatementHandler implements StatementHandler {
  /**
   * 靜態代理模式
   */
  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 根據{@link org.apache.ibatis.mapping.StatementType} 來建立不一樣的實現類 (策略模式)
    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());
    }
  }

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }
...
}

BaseStatementHandler

全部子類的抽象父類,定義了初始化statement的操做順序,由子類實現具體的實例化不一樣的statement(模板模式)。apache

@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    // 實例化Statement(由子類實現)【模板方法+策略模式】
    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);
  }
}

protected abstract Statement instantiateStatement(Connection connection) throws SQLException;

instantiateStatement()就是一個模板方法,由子類實現。mybatis

SimpleStatementHandler

使用JDBCStatement執行模式,不須要作參數處理,源碼以下:app

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  // 實例化Statement
  if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
    return connection.createStatement();
  } else {
    return connection.createStatement(mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  }
}

@Override
public void parameterize(Statement statement) {
  // N/A
  // 使用Statement是直接執行sql 因此沒有參數
}

PreparedStatementHandler

使用JDBCPreparedStatement預編譯執行模式。框架

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  // 實例化PreparedStatement
  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() == ResultSetType.DEFAULT) {
    return connection.prepareStatement(sql);
  } else {
    return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
  }
}

@Override
public void parameterize(Statement statement) throws SQLException {
  // 參數處理
  parameterHandler.setParameters((PreparedStatement) statement);
}

CallableStatementHandler

使用JDBCCallableStatement執行模式,用來調用存儲過程。如今不多用。

ParameterHandler

主要做用是給PreparedStatement設置參數,源碼以下:

@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;
        }
        // 判斷參數是否有相應的 TypeHandler
        else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          // 以上都不是,經過反射獲取value值
          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設置參數
          typeHandler.setParameter(ps, i + 1, value, jdbcType);
        } catch (TypeException | SQLException e) {
          throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
        }
      }
    }
  }
}
  1. 獲取參數映射關係
  2. 獲取參數名稱
  3. 根據參數名稱獲取參數值
  4. 獲取參數的類型處理器
  5. 根據TypeHandler設置參數值

經過上面的流程能夠發現,真正設置參數是由TypeHandler來實現的。

TypeHandler

Mybatis基本上提供了咱們所須要用到的全部TypeHandler,固然咱們也能夠本身實現。TypeHandler的主要做用是:

  1. 設置PreparedStatement參數值
  2. 獲取查詢結果值
public interface TypeHandler<T> {

  /**
   * 給{@link PreparedStatement}設置參數值
   *
   * @param ps        {@link PreparedStatement}
   * @param i         參數的索引位
   * @param parameter 參數值
   * @param jdbcType  參數類型
   * @throws SQLException
   */
  void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;

  /**
   * 根據列名獲取結果值
   *
   * @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>
   */
  T getResult(ResultSet rs, String columnName) throws SQLException;

  /**
   * 根據索引位獲取結果值
   */
  T getResult(ResultSet rs, int columnIndex) throws SQLException;

  /**
   * 獲取存儲過程結果值
   */
  T getResult(CallableStatement cs, int columnIndex) throws SQLException;

}

TypeHandler的本質就是對JDBC中stmt.setString(1, "wyf");resultSet.getString("name")的封裝,JDBC完整代碼能夠查看JDBC 面試要點

ResultSetHandler

ResultSetHandler主要做用是:對數據庫返回的結果集(ResultSet)進行封裝,經過經過ResultMap配置和反射完成自動映射,返回用戶指定的實體類型;核心思路以下:

  1. 根據RowBounds作分頁處理
  2. 根據ResultMap配置的返回值類型和constructor配置信息實例化目標類
  3. 根據ResultMap配置的映射關係,獲取到TypeHandler,進而從ResultSet中獲取值
  4. 根據ResultMap配置的映射關係,獲取到目標類的屬性名稱,而後經過反射給目標類賦值

源碼太多了,這裏就不發了,有興趣就在下面的源碼上看註釋吧,下面的流程圖會畫出方法的調用棧。

Mybatis自帶的RowBounds分頁是邏輯分頁,數據量大了有可能會內存溢出,因此不建議使用Mybatis默認分頁。

數據讀取流程圖

總結

Mybatis的整個數據讀取流程其實就是對JDBC的一個標準實現。

Mybatis 源碼中文註釋

https://github.com/xiaolyuh/mybatis

相關文章
相關標籤/搜索