欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

正文

mybatis版本:3.5.12

mybatis通過Executor查詢出結(jié)果后,通常返回的是一個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ù)會拋出TooManyResultsException的異常。
  • 比如用戶調(diào)用SqlSession#selectMap就是遍歷List中的每個元素,把這些元素轉(zhuǎn)換成key-value形式的Map結(jié)構(gòu)并返回
  • 或者用戶自定義返回一個User對象,也會遍歷List,把元素轉(zhuǎn)換為指定類型的對象

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

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

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

它的邏輯在ResultSetHandler#handleResultSets方法中

ResultSetHandler#handleResultSets

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

public List<Object> handleResultSets(Statement stmt) throws SQLException {
  // 第一部分:用來緩存最后的返回值,每條記錄處理完之后都會存入該集合中
  final List<Object> multipleResults = new ArrayList<>();
  int resultSetCount = 0;
  /*
   * ResultSetWrapper對結(jié)果集(ResultSet)進行了包裝
   * getFirstResultSet獲取了Statement中的第一個結(jié)果集對象,注:一般情況下只有一個結(jié)果集,如果調(diào)用存儲過程可能就會獲得多個結(jié)果集
   */
  ResultSetWrapper rsw = getFirstResultSet(stmt);
  // 第二部分
  // 1. 先處理mappedStatement中的ResultMap標簽(每個XML的SQL語句都被映射成了MappedStatement對象。)
  // 每個SQL執(zhí)行的返回結(jié)果有可能是多個resultMap標簽共同組成的??赡苁嵌嘟Y(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);
    // 獲取下一個結(jié)果集(多結(jié)果集情況)
    rsw = getNextResultSet(stmt);
    // nestedResultObjects清空該對象,該對象是一個緩存
    cleanUpAfterHandlingResultSet();
    resultSetCount++;
  }
  // 第四部分
  // 2. 先處理mappedStatement中的ResultSets標簽. 因為解析ResultMap的時候,可能ResultMap中包含ResultSet標簽,而ResultSet標簽并未解析
  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);
}

該代碼主要分為兩個大邏輯:

  • 通過getFirstResultSet方法獲取第一個結(jié)果集對象,然后循環(huán)ps中的結(jié)果集,處理每個結(jié)果集。每個結(jié)果集處理完后的數(shù)據(jù)存放到multipleResults這個集合中
  • 處理多結(jié)果集剩余的部分。因為用戶可能使用了resultSets標簽。返回2個結(jié)果集,但是在處理第一個結(jié)果集映射成用戶指定類型時,需要用到第二個結(jié)果集對象,這在第一步是無法完成的。只能在第二部完成。

比如有如下存儲函數(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的順序不能隨意放置,否則會導致結(jié)果集為空-->
<select id="selectMoreResults2" statementType="CALLABLE" resultSets="users,orders" resultMap="userMap">
    {call get_user_and_orders(1)}
</select>

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

下面我們詳細分析上面這一長串代碼。

第一部分:ResultSetWrapper

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

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

第二部分:驗證rsw對象

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

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

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

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

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

改代碼的意思是,當rsw存在并且resultMapCount > resultSetCount時

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

第四部分:處理ResultSets標簽

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

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

第五部分:collapseSingleResultList

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

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

總結(jié)

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

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

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

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

相關(guān)文章

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

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

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

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

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

    JPA延遲加載no Session報錯解決分析

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

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

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

    Mybatis-Plus樂觀鎖配置流程

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

    詳解spring boot中使用JdbcTemplate

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

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

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

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

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

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

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

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

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

最新評論