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

數(shù)據(jù)讀寫的本質(zhì)

不管是哪種ORM框架,數(shù)據(jù)讀寫其本質(zhì)都是對(duì)JDBC的封裝,其目的主要都是簡(jiǎn)化JDBC的開發(fā)流程喝滞,進(jìn)而讓開發(fā)人員更關(guān)注業(yè)務(wù)。下面是JDBC的核心流程:

  1. 注冊(cè) JDBC 驅(qū)動(dòng)(Class.forName("XXX");)
  2. 打開連接(DriverManager.getConnection("url","name","password"))
  3. 根據(jù)連接尤勋,創(chuàng)建 Statement(conn.prepareStatement(sql))
  4. 設(shè)置參數(shù)(stmt.setString(1, "wyf");)
  5. 執(zhí)行查詢(stmt.executeQuery();)
  6. 處理結(jié)果,結(jié)果集映射(resultSet.next())
  7. 關(guān)閉資源(finally)

Mybatis也是在對(duì)JDBC進(jìn)行封裝茵宪,它將注冊(cè)驅(qū)動(dòng)打開連接交給了數(shù)據(jù)庫連接池來負(fù)責(zé)最冰,Mybatis支持第三方數(shù)據(jù)庫連接池,也可以使用自帶的數(shù)據(jù)庫連接池稀火;創(chuàng)建 Statement執(zhí)行查詢交給了StatementHandler來負(fù)責(zé)锌奴;設(shè)置參數(shù)交給ParameterHandler來負(fù)責(zé);最后兩步交給了ResultSetHandler來負(fù)責(zé)憾股。

Executor內(nèi)部運(yùn)作過程

測(cè)試方法org.apache.ibatis.binding.BindingTest#shouldFindThreeSpecificPosts

下面是一個(gè)簡(jiǎn)單的數(shù)據(jù)讀寫過程,我們?cè)诤暧^上先來了解一下每一個(gè)組件在整個(gè)數(shù)據(jù)讀寫上的作用:

Executor內(nèi)部運(yùn)作過程.png

Mybatis的數(shù)據(jù)讀寫主要是通Excuter來協(xié)調(diào)StatementHandler箕慧、ParameterHandler和ResultSetHandler三個(gè)組件來實(shí)現(xiàn)的:

  • StatementHandler:它的作用是使用數(shù)據(jù)庫的Statement或PrepareStatement執(zhí)行操作服球,啟承上啟下作用;
  • ParameterHandler:對(duì)預(yù)編譯的SQL語句進(jìn)行參數(shù)設(shè)置颠焦,SQL語句中的的占位符“斩熊?”都對(duì)應(yīng)BoundSql.parameterMappings集合中的一個(gè)元素,在該對(duì)象中記錄了對(duì)應(yīng)的參數(shù)名稱以及該參數(shù)的相關(guān)屬性
  • ResultSetHandler:對(duì)數(shù)據(jù)庫返回的結(jié)果集(ResultSet)進(jìn)行封裝伐庭,返回用戶指定的實(shí)體類型粉渠;

StatementHandler

StatementHandler類圖

StatementHandler.png

RoutingStatementHandler

通過StatementType來創(chuàng)建StatementHandler,使用靜態(tài)代理模式來完成方法的調(diào)用圾另,主要起到路由作用霸株。它是Excutor組件真正實(shí)例化的組件。

public class RoutingStatementHandler implements StatementHandler {
  /**
   * 靜態(tài)代理模式
   */
  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 根據(jù){@link org.apache.ibatis.mapping.StatementType} 來創(chuàng)建不同的實(shí)現(xiàn)類 (策略模式)
    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的操作順序去件,由子類實(shí)現(xiàn)具體的實(shí)例化不同的statement(模板模式)。

@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
  ErrorContext.instance().sql(boundSql.getSql());
  Statement statement = null;
  try {
    // 實(shí)例化Statement(由子類實(shí)現(xiàn))【模板方法+策略模式】
    statement = instantiateStatement(connection);
    // 設(shè)置超時(shí)時(shí)間
    setStatementTimeout(statement, transactionTimeout);
    // 設(shè)置獲取數(shù)據(jù)記錄條數(shù)
    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()就是一個(gè)模板方法,由子類實(shí)現(xiàn)尤溜。

SimpleStatementHandler

使用JDBCStatement執(zhí)行模式倔叼,不需要做參數(shù)處理,源碼如下:

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  // 實(shí)例化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是直接執(zhí)行sql 所以沒有參數(shù)
}

PreparedStatementHandler

使用JDBCPreparedStatement預(yù)編譯執(zhí)行模式宫莱。

@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
  // 實(shí)例化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 {
  // 參數(shù)處理
  parameterHandler.setParameters((PreparedStatement) statement);
}

CallableStatementHandler

使用JDBCCallableStatement執(zhí)行模式丈攒,用來調(diào)用存儲(chǔ)過程。現(xiàn)在很少用授霸。

ParameterHandler

主要作用是給PreparedStatement設(shè)置參數(shù)巡验,源碼如下:

@Override
public void setParameters(PreparedStatement ps) {
  ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
  // 獲取參數(shù)映射關(guān)系
  List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
  if (parameterMappings != null) {
    // 循環(huán)獲取處理參數(shù)
    for (int i = 0; i < parameterMappings.size(); i++) {
      // 獲取對(duì)應(yīng)索引位的參數(shù)
      ParameterMapping parameterMapping = parameterMappings.get(i);
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        // 獲取參數(shù)名稱
        String propertyName = parameterMapping.getProperty();
        // 判斷是否是附加參數(shù)
        if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
          value = boundSql.getAdditionalParameter(propertyName);
        }
        // 判斷是否是沒有參數(shù)
        else if (parameterObject == null) {
          value = null;
        }
        // 判斷參數(shù)是否有相應(yīng)的 TypeHandler
        else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          // 以上都不是,通過反射獲取value值
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        // 獲取參數(shù)的類型處理器
        TypeHandler typeHandler = parameterMapping.getTypeHandler();
        JdbcType jdbcType = parameterMapping.getJdbcType();
        if (value == null && jdbcType == null) {
          jdbcType = configuration.getJdbcTypeForNull();
        }
        try {
          // 根據(jù)TypeHandler設(shè)置參數(shù)
          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. 獲取參數(shù)映射關(guān)系
  2. 獲取參數(shù)名稱
  3. 根據(jù)參數(shù)名稱獲取參數(shù)值
  4. 獲取參數(shù)的類型處理器
  5. 根據(jù)TypeHandler設(shè)置參數(shù)值

通過上面的流程可以發(fā)現(xiàn)绝葡,真正設(shè)置參數(shù)是由TypeHandler來實(shí)現(xiàn)的深碱。

TypeHandler

Mybatis基本上提供了我們所需要用到的所有TypeHandler,當(dāng)然我們也可以自己實(shí)現(xiàn)藏畅。TypeHandler的主要作用是:

  1. 設(shè)置PreparedStatement參數(shù)值
  2. 獲取查詢結(jié)果值
public interface TypeHandler<T> {

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

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

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

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

}

TypeHandler的本質(zhì)就是對(duì)JDBC中stmt.setString(1, "wyf");resultSet.getString("name")的封裝敷硅,JDBC完整代碼可以查看JDBC 面試要點(diǎn)

ResultSetHandler

ResultSetHandler主要作用是:對(duì)數(shù)據(jù)庫返回的結(jié)果集(ResultSet)進(jìn)行封裝愉阎,通過通過ResultMap配置和反射完成自動(dòng)映射绞蹦,返回用戶指定的實(shí)體類型;核心思路如下:

  1. 根據(jù)RowBounds做分頁處理
  2. 根據(jù)ResultMap配置的返回值類型和constructor配置信息實(shí)例化目標(biāo)類
  3. 根據(jù)ResultMap配置的映射關(guān)系榜旦,獲取到TypeHandler幽七,進(jìn)而從ResultSet中獲取值
  4. 根據(jù)ResultMap配置的映射關(guān)系,獲取到目標(biāo)類的屬性名稱溅呢,然后通過反射給目標(biāo)類賦值

源碼太多了澡屡,這里就不發(fā)了,有興趣就在下面的源碼上看注釋吧咐旧,下面的流程圖會(huì)畫出方法的調(diào)用棧驶鹉。

Mybatis自帶的RowBounds分頁是邏輯分頁,數(shù)據(jù)量大了有可能會(huì)內(nèi)存溢出铣墨,所以不建議使用Mybatis默認(rèn)分頁室埋。

數(shù)據(jù)讀取流程圖

mybatis數(shù)據(jù)讀寫流程1.png
mybatis數(shù)據(jù)讀寫流程2.png

總結(jié)

Mybatis的整個(gè)數(shù)據(jù)讀取流程其實(shí)就是對(duì)JDBC的一個(gè)標(biāo)準(zhǔn)實(shí)現(xiàn)。

Mybatis 源碼中文注釋

https://github.com/xiaolyuh/mybatis

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末伊约,一起剝皮案震驚了整個(gè)濱河市姚淆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屡律,老刑警劉巖腌逢,帶你破解...
    沈念sama閱讀 211,123評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異超埋,居然都是意外死亡上忍,警方通過查閱死者的電腦和手機(jī)骤肛,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,031評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來窍蓝,“玉大人腋颠,你說我怎么就攤上這事∠朋希” “怎么了淑玫?”我有些...
    開封第一講書人閱讀 156,723評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)面睛。 經(jīng)常有香客問我絮蒿,道長(zhǎng),這世上最難降的妖魔是什么叁鉴? 我笑而不...
    開封第一講書人閱讀 56,357評(píng)論 1 283
  • 正文 為了忘掉前任土涝,我火速辦了婚禮,結(jié)果婚禮上幌墓,老公的妹妹穿的比我還像新娘但壮。我一直安慰自己,他們只是感情好常侣,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,412評(píng)論 5 384
  • 文/花漫 我一把揭開白布蜡饵。 她就那樣靜靜地躺著,像睡著了一般胳施。 火紅的嫁衣襯著肌膚如雪溯祸。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,760評(píng)論 1 289
  • 那天舞肆,我揣著相機(jī)與錄音焦辅,去河邊找鬼。 笑死椿胯,一個(gè)胖子當(dāng)著我的面吹牛氨鹏,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播压状,決...
    沈念sama閱讀 38,904評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼跟继!你這毒婦竟也來了种冬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,672評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤舔糖,失蹤者是張志新(化名)和其女友劉穎娱两,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體金吗,經(jīng)...
    沈念sama閱讀 44,118評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡十兢,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,456評(píng)論 2 325
  • 正文 我和宋清朗相戀三年趣竣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片旱物。...
    茶點(diǎn)故事閱讀 38,599評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡遥缕,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出宵呛,到底是詐尸還是另有隱情单匣,我是刑警寧澤,帶...
    沈念sama閱讀 34,264評(píng)論 4 328
  • 正文 年R本政府宣布宝穗,位于F島的核電站户秤,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏逮矛。R本人自食惡果不足惜鸡号,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,857評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望须鼎。 院中可真熱鬧鲸伴,春花似錦、人聲如沸莉兰。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,731評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽糖荒。三九已至杉辙,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間捶朵,已是汗流浹背蜘矢。 一陣腳步聲響...
    開封第一講書人閱讀 31,956評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留综看,地道東北人品腹。 一個(gè)月前我還...
    沈念sama閱讀 46,286評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像红碑,于是被迫代替她去往敵國(guó)和親舞吭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,465評(píng)論 2 348