亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

詳解MyBatis?ResultSetHandler?結(jié)果集的解析過程

 更新時(shí)間:2023年02月13日 09:15:23   作者:念念清晰  
這篇文章主要為大家介紹了MyBatis?ResultSetHandler?結(jié)果集的解析過程,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

正文

mybatis版本:3.5.12

mybatis通過Executor查詢出結(jié)果后,通常返回的是一個(gè)List結(jié)構(gòu),再根據(jù)用戶調(diào)用的API把List結(jié)構(gòu)轉(zhuǎn)為指定結(jié)構(gòu)。

  • 比如用戶調(diào)用SqlSession#selectOne就是List中只有一條數(shù)據(jù),如果查詢得到多條數(shù)據(jù)會(huì)拋出TooManyResultsException的異常。
  • 比如用戶調(diào)用SqlSession#selectMap就是遍歷List中的每個(gè)元素,把這些元素轉(zhuǎn)換成key-value形式的Map結(jié)構(gòu)并返回
  • 或者用戶自定義返回一個(gè)User對(duì)象,也會(huì)遍歷List,把元素轉(zhuǎn)換為指定類型的對(duì)象

mybatis中封裝了一個(gè)類叫做ResultSetHandler它用來處理查詢數(shù)據(jù)庫得到的結(jié)果集,并把結(jié)果集解析為用戶指定類型的數(shù)據(jù)。它的調(diào)用時(shí)機(jī)就是在查詢玩數(shù)據(jù)庫之后,調(diào)用時(shí)機(jī)如下

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

第一步先獲取PreparedStatement對(duì)象,第二部執(zhí)行execute方法查詢數(shù)據(jù)庫,第三步就是使用ResultSetHandler處理結(jié)果集。接下來就來看下resultSetHandler是如何處理結(jié)果集對(duì)象的。

它的邏輯在ResultSetHandler#handleResultSets方法中

ResultSetHandler#handleResultSets

ResultSetHandler是一個(gè)接口,它只有一個(gè)實(shí)現(xiàn)類DefaultResultSetHandler,下面是handleResultSets方法關(guān)的鍵代碼。我把此核心邏輯代碼分為了5部分,后面章節(jié)詳細(xì)介紹

public List<Object> handleResultSets(Statement stmt) throws SQLException {
  // 第一部分:用來緩存最后的返回值,每條記錄處理完之后都會(huì)存入該集合中
  final List<Object> multipleResults = new ArrayList<>();
  int resultSetCount = 0;
  /*
   * ResultSetWrapper對(duì)結(jié)果集(ResultSet)進(jìn)行了包裝
   * getFirstResultSet獲取了Statement中的第一個(gè)結(jié)果集對(duì)象,注:一般情況下只有一個(gè)結(jié)果集,如果調(diào)用存儲(chǔ)過程可能就會(huì)獲得多個(gè)結(jié)果集
   */
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  // 第二部分
  // 1. 先處理mappedStatement中的ResultMap標(biāo)簽(每個(gè)XML的SQL語句都被映射成了MappedStatement對(duì)象。)
  // 每個(gè)SQL執(zhí)行的返回結(jié)果有可能是多個(gè)resultMap標(biāo)簽共同組成的。可能是多結(jié)果集
  List<ResultMap> resultMaps = mappedStatement.getResultMaps();
  int resultMapCount = resultMaps.size();
  validateResultMapsCount(rsw, resultMapCount);
  // 第三部分
  while (rsw != null && resultMapCount > resultSetCount) {
    // MappedStatement中的ResultMap數(shù)量應(yīng)該和 結(jié)果集的數(shù)量一致
    ResultMap resultMap = resultMaps.get(resultSetCount);
    // 處理結(jié)果集,這是該方法中最重要的步驟
    handleResultSet(rsw, resultMap, multipleResults, null);
    // 獲取下一個(gè)結(jié)果集(多結(jié)果集情況)
    rsw = getNextResultSet(stmt);
    // nestedResultObjects清空該對(duì)象,該對(duì)象是一個(gè)緩存
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }
  // 第四部分
  // 2. 先處理mappedStatement中的ResultSets標(biāo)簽. 因?yàn)榻馕鯮esultMap的時(shí)候,可能ResultMap中包含ResultSet標(biāo)簽,而ResultSet標(biāo)簽并未解析
  String[] resultSets = mappedStatement.getResultSets();
  if (resultSets != null) {
    while (rsw != null && resultSetCount < resultSets.length) {
      ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
      if (parentMapping != null) {
        String nestedResultMapId = parentMapping.getNestedResultMapId();
        ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
        handleResultSet(rsw, resultMap, null, parentMapping);
      }
      rsw = getNextResultSet(stmt);
      cleanUpAfterHandlingResultSet();
      resultSetCount++;
    }
  }
  // 第五部分
  return collapseSingleResultList(multipleResults);
}

該代碼主要分為兩個(gè)大邏輯:

  • 通過getFirstResultSet方法獲取第一個(gè)結(jié)果集對(duì)象,然后循環(huán)ps中的結(jié)果集,處理每個(gè)結(jié)果集。每個(gè)結(jié)果集處理完后的數(shù)據(jù)存放到multipleResults這個(gè)集合中
  • 處理多結(jié)果集剩余的部分。因?yàn)橛脩艨赡苁褂昧藃esultSets標(biāo)簽。返回2個(gè)結(jié)果集,但是在處理第一個(gè)結(jié)果集映射成用戶指定類型時(shí),需要用到第二個(gè)結(jié)果集對(duì)象,這在第一步是無法完成的。只能在第二部完成。

比如有如下存儲(chǔ)函數(shù):getuserand_orders

create procedure get_user_and_orders(in id int)
begin
    select * from user;
    select * from order;
END;

該函數(shù)的業(yè)務(wù)意義是:查詢所有的用戶,和所有的訂單。在Mapper中定義的resultMap如下

<resultMap id="userMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="password" column="password"/>
    <association property="orderList" resultSet="orders">
        <result property="name" column="name"/>
    </association>
</resultMap>
<!--resultSets的順序不能隨意放置,否則會(huì)導(dǎo)致結(jié)果集為空-->
<select id="selectMoreResults2" statementType="CALLABLE" resultSets="users,orders" resultMap="userMap">
    {call get_user_and_orders(1)}
</select>

此時(shí)如果用戶執(zhí)行了存儲(chǔ)函數(shù),那么PS中的結(jié)果集 會(huì)有兩個(gè),分別是users和orders。mybatis在處理結(jié)果集時(shí)發(fā)現(xiàn)。結(jié)果集中有兩個(gè)對(duì)象,先處理第一個(gè),第一個(gè)結(jié)果集為users,自然要映射為User對(duì)象,給User對(duì)象的orderList屬性賦值時(shí)發(fā)現(xiàn)結(jié)果集中沒有關(guān)于訂單的數(shù)據(jù),因?yàn)橛唵蔚臄?shù)據(jù)在第二個(gè)結(jié)果集中。這時(shí)候就會(huì)在第二部再去處理第二個(gè)結(jié)果集。把訂單的結(jié)果集數(shù)據(jù)映射到User的orderList屬性中。

下面我們?cè)敿?xì)分析上面這一長串代碼。

第一部分:ResultSetWrapper

首先我們來看第一部分的三行代碼

final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
  • 首先定義了一個(gè)List類型的集合multipleResults,結(jié)果集中每一條記錄解析完畢后的數(shù)據(jù)都會(huì)存放到該集合中
  • 定義變量resultSetCount,它代表結(jié)果集的個(gè)數(shù)。(結(jié)果集的個(gè)數(shù)不一定等于ResultMaps的個(gè)數(shù)哦)
  • 把結(jié)果集對(duì)象封裝為一個(gè)ResultSetWrapper對(duì)象。ResultSetWrapper其實(shí)就是對(duì)JDBC中的ResultSet對(duì)象做了一個(gè)封裝。包裝了一些元數(shù)據(jù)的信息。下面來看下ResultSetWrapper的重要結(jié)構(gòu)
public class ResultSetWrapper {
  private final ResultSet resultSet;
  private final TypeHandlerRegistry typeHandlerRegistry;
  // 結(jié)果集中的列名集合
  private final List<String> columnNames = new ArrayList<>();
  // java名稱集合
  private final List<String> classNames = new ArrayList<>();
  private final List<JdbcType> jdbcTypes = new ArrayList<>();
  // ResultMap標(biāo)簽中指定的映射(重要!)
  private final Map<String, List<String>> mappedColumnNamesMap = new HashMap<>();
  // ResultMap標(biāo)簽中未指定的映射字段(重要?。?
  private final Map<String, List<String>> unMappedColumnNamesMap = new HashMap<>();
}
  • resultSet:JDBC中的結(jié)果集對(duì)象
  • TypeHandlerRegistry:類型處理器,用于JDBC和Java類型的轉(zhuǎn)換
  • columnNames:結(jié)果集中的所有列名的集合
  • classNames:每一列對(duì)應(yīng)的Java類型的集合
  • jdbcTypes:每一列對(duì)應(yīng)的JDBC類型的結(jié)合
  • mappedColumnNamesMap:resultMap標(biāo)簽中顯式定義的標(biāo)簽Map
  • unMappedColumnNamesMap:結(jié)果集中返回但resultMap標(biāo)簽中未定義的列會(huì)被記錄在該Map中

第二部分:驗(yàn)證rsw對(duì)象

上面獲取了rsw對(duì)象(ResultSetWrapper,后面簡(jiǎn)稱rsw了)后,接下來需要驗(yàn)證rsw對(duì)象。第二部分的三行代碼如下

List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
  • 首先先從mappedStatement對(duì)象中獲取ResultMap對(duì)象,首先mappedStatement可以理解為XML中的每個(gè)select|update|insert|delete節(jié)點(diǎn)都被封裝成了MS對(duì)象(mappedStatement簡(jiǎn)稱MS)。那么MS對(duì)象中其實(shí)就包含了每個(gè)select|update|insert|delete節(jié)點(diǎn)的信息。而一個(gè)節(jié)點(diǎn)可能會(huì)在resultMap標(biāo)簽上定義多個(gè)返回結(jié)果集。比如下面代碼在select標(biāo)簽的resultMap屬性中定義了兩個(gè)結(jié)果集
<select id="selectMoreResults1" statementType="CALLABLE" resultMap="users,authors">
    {call get_user_and_authors(1)}
</select>
  • 定義變量resultMapCount,它表示一個(gè)MS對(duì)象中resultMap的個(gè)數(shù)。通常它是1。我們常用的情況也是1。要注意,第一部分定義的resultSetCount變量和resultMapCount并不一定相等。比如PreparedStatement對(duì)象中有兩個(gè)結(jié)果集——那么此時(shí)的resultSetCount就是2.但是xml中select標(biāo)簽的resultMap屬性只定義了一個(gè)映射——那么此時(shí)的resultMapCount就是1
  • 當(dāng)resultMapCount < resultSetCount的時(shí)候,就說明多個(gè)結(jié)果集對(duì)應(yīng)了XML中的一個(gè)映射關(guān)系,此時(shí)就需要解析resultSet標(biāo)簽
  • 最后一件事是驗(yàn)證rsw是否合法,代碼比較簡(jiǎn)單就不詳細(xì)介紹了

第三部分:遍歷rsw中的結(jié)果集

接下來就是要遍歷rsw中的結(jié)果集對(duì)象。并把結(jié)果集中的每條記錄都根據(jù)resultMap標(biāo)簽定義的映射關(guān)系轉(zhuǎn)化為指定類型的數(shù)據(jù)。并把它加入到第一部分提到的multipleResults集合中。第三部分的代碼如下

while (rsw != null && resultMapCount > resultSetCount) {
  ResultMap resultMap = resultMaps.get(resultSetCount);
  // 處理結(jié)果集,這是該方法中最重要的步驟
  handleResultSet(rsw, resultMap, multipleResults, null);
  // 獲取下一個(gè)結(jié)果集(多結(jié)果集情況)
  rsw = getNextResultSet(stmt);
  // nestedResultObjects清空該對(duì)象,該對(duì)象是一個(gè)緩存
  cleanUpAfterHandlingResultSet();
  resultSetCount++;
}

改代碼的意思是,當(dāng)rsw存在并且resultMapCount > resultSetCount時(shí)

  • 獲取結(jié)果集對(duì)應(yīng)的ResultMap對(duì)象
  • 調(diào)用handleResultSet方法處理結(jié)果集對(duì)象(這個(gè)方法很重要,它實(shí)際上完成了結(jié)果集中的每條記錄的解析,它其中又調(diào)用了很多重要的方法,該方法后面我會(huì)單獨(dú)抽出一篇文章來講)
  • 獲取下一個(gè)結(jié)果集并且空緩存對(duì)象。nestedResultObjects是解析嵌套映射中的一個(gè)緩存對(duì)象(了解即可)每次解析完一個(gè)結(jié)果集后都要清空該對(duì)象。
  • 重復(fù)上述步驟。不過一般我們都是執(zhí)行單條SQL語句,所以PreparedStatement一般只有一個(gè)結(jié)果集,該循環(huán)也只會(huì)走一次。除非調(diào)用了存儲(chǔ)函數(shù)

第四部分:處理ResultSets標(biāo)簽

如果在第一部分到第三部分的循環(huán)中,順序處理完結(jié)果集對(duì)象之后,resultSetCount數(shù)量還是大于resultMapCount,那么就證明PS對(duì)象返回的是多結(jié)果集,并且多結(jié)果集值對(duì)應(yīng)了一個(gè)映射關(guān)系,此時(shí)就需要解析這個(gè)ResultSets標(biāo)簽。它的解析流程和第三部分一樣,重點(diǎn)就在于handleResultSet方法。下面使用一個(gè)案例來詳細(xì)說明為什么會(huì)有這部分的解析。

  • 定義一個(gè)存儲(chǔ)函數(shù)
create procedure get_user_and_orders(in id int)
begin
    select * from user;
    select * from order;
END;
  • xml中配置調(diào)用存儲(chǔ)函數(shù)的select節(jié)點(diǎn)
<resultMap id="userMap" type="user">
    <id property="id" column="id"/>
    <result property="username" column="username"/>
    <result property="birthday" column="birthday"/>
    <result property="password" column="password"/>
    <association property="orderList" resultSet="orders">
        <result property="name" column="name"/>
    </association>
</resultMap>
<!--resultSets的順序不能隨意放置,否則會(huì)導(dǎo)致結(jié)果集為空-->
<select id="selectMoreResults2" statementType="CALLABLE" resultSets="users,orders" resultMap="userMap">
    {call get_user_and_orders(1)}
</select>
  • 用戶調(diào)用selectMoreResults2這個(gè)方法。很顯然selectMoreResults2的返回結(jié)果就是存儲(chǔ)函數(shù)執(zhí)行的結(jié)果,它執(zhí)行了兩個(gè)select語句,意味著會(huì)生成兩個(gè)結(jié)果集對(duì)象,xml中select標(biāo)簽定義該存儲(chǔ)函數(shù)的執(zhí)行結(jié)果值對(duì)應(yīng)一個(gè)映射關(guān)系就是userMap。但是兩個(gè)結(jié)果集怎么映射成一個(gè)resultMap呢?我們真正想要的結(jié)果是把第二個(gè)結(jié)果集映射到userMap中的orderList屬性。所以在進(jìn)行第三部分進(jìn)行遍歷的時(shí)候,循環(huán)只會(huì)走一次,因?yàn)閞esultSetCount=2,resultMapCount=1,讀者可以自定使用該業(yè)務(wù)代碼進(jìn)行斷點(diǎn)調(diào)試。在解析第一個(gè)結(jié)果集時(shí)發(fā)現(xiàn)第一個(gè)結(jié)果集中沒有orderList的信息。無法完成映射。所以才會(huì)走到第四部分進(jìn)行結(jié)果集映射!

第五部分:collapseSingleResultList

最后一部分很簡(jiǎn)單,它只是把最后返回的結(jié)果進(jìn)行判斷:如果返回結(jié)果multipleResults集合大小為1,則只返回集合中的這個(gè)元素,否則返回原對(duì)象本身

private List<Object> collapseSingleResultList(List<Object> multipleResults) {
  return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
}

總結(jié)

該篇講述了mybatis在執(zhí)行完數(shù)據(jù)庫后進(jìn)行結(jié)果集的大致解析過程。

  • ResultSetWrapper是對(duì)JDBC中的ResultSet對(duì)象的封裝
  • 結(jié)果集解析的重點(diǎn)在DefaultResultSetHandler#handleResultSet這個(gè)方法中
  • XML中的resultMap可以定義多個(gè)映射關(guān)系。如果多個(gè)結(jié)果集對(duì)應(yīng)一個(gè)映射關(guān)系就需要第四部分(對(duì)resultSets標(biāo)簽的處理)

后續(xù)我會(huì)帶來handleResultSet方法的解析~

更多關(guān)于MyBatis ResultSetHandler結(jié)果集的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • windows下使用 intellij idea 編譯 kafka 源碼環(huán)境

    windows下使用 intellij idea 編譯 kafka 源碼環(huán)境

    這篇文章主要介紹了使用 intellij idea 編譯 kafka 源碼的環(huán)境,本文是基于windows下做的項(xiàng)目演示,需要的朋友可以參考下
    2021-10-10
  • RabbitMQ消息有效期與死信的處理過程

    RabbitMQ消息有效期與死信的處理過程

    利用DLX,當(dāng)消息在一個(gè)隊(duì)列中變成死信?(dead?message)?之后,它能被重新publish到另一個(gè)Exchange,這個(gè)Exchange就是DLX,本文重點(diǎn)給大家介紹RabbitMQ消息有效期與死信的相關(guān)知識(shí),感興趣的朋友跟隨小編一起看看吧
    2022-03-03
  • JPA延遲加載no Session報(bào)錯(cuò)解決分析

    JPA延遲加載no Session報(bào)錯(cuò)解決分析

    這篇文章主要為大家介紹了JPA延遲加載no Session報(bào)錯(cuò)解決分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • SpringBoot啟動(dòng)多數(shù)據(jù)源找不到合適的驅(qū)動(dòng)類問題

    SpringBoot啟動(dòng)多數(shù)據(jù)源找不到合適的驅(qū)動(dòng)類問題

    這篇文章主要介紹了SpringBoot啟動(dòng)多數(shù)據(jù)源找不到合適的驅(qū)動(dòng)類問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • Mybatis-Plus樂觀鎖配置流程

    Mybatis-Plus樂觀鎖配置流程

    這篇文章主要介紹了Mybatis-Plus樂觀鎖配置使用流程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作有一定的參考學(xué)習(xí)價(jià)值,感興趣的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2024-01-01
  • 詳解spring boot中使用JdbcTemplate

    詳解spring boot中使用JdbcTemplate

    JdbcTemplate 是在JDBC API基礎(chǔ)上提供了更抽象的封裝,并提供了基于方法注解的事務(wù)管理能力。 通過使用SpringBoot自動(dòng)配置功能并代替我們自動(dòng)配置beans,下面給大家介紹spring boot中使用JdbcTemplate相關(guān)知識(shí),一起看看吧
    2017-04-04
  • Java經(jīng)典設(shè)計(jì)模式之責(zé)任鏈模式原理與用法詳解

    Java經(jīng)典設(shè)計(jì)模式之責(zé)任鏈模式原理與用法詳解

    這篇文章主要介紹了Java經(jīng)典設(shè)計(jì)模式之責(zé)任鏈模式,簡(jiǎn)單說明了責(zé)任鏈模式的概念、原理,并結(jié)合實(shí)例形式分析了java實(shí)現(xiàn)責(zé)任鏈模式的具體用法與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2017-08-08
  • 使用Springboot 打jar包實(shí)現(xiàn)分離依賴lib和配置

    使用Springboot 打jar包實(shí)現(xiàn)分離依賴lib和配置

    這篇文章主要介紹了使用Springboot 打jar包實(shí)現(xiàn)分離依賴lib和配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • Springboot上傳文件時(shí)提示405問題及排坑過程

    Springboot上傳文件時(shí)提示405問題及排坑過程

    這篇文章主要介紹了Springboot上傳文件時(shí)提示405問題及排坑過程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-07-07
  • SpringMVC跨服務(wù)器上傳文件中出現(xiàn)405錯(cuò)誤的解決

    SpringMVC跨服務(wù)器上傳文件中出現(xiàn)405錯(cuò)誤的解決

    這篇文章主要介紹了SpringMVC跨服務(wù)器上傳文件中出現(xiàn)405錯(cuò)誤的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09

最新評(píng)論