主頁 > 軟體設計 > 精通Mybatis之結果集處理流程與映射體系(重點mybatis嵌套子查詢,回圈依賴解決方案)(二)

精通Mybatis之結果集處理流程與映射體系(重點mybatis嵌套子查詢,回圈依賴解決方案)(二)

2021-05-07 11:56:42 軟體設計

前言

大家五一快樂啊,上次小撰寫了映射體系一,具體講了MetaObject反射工具的使用以及原始碼解釋,接下來講一下真正的映射體系,

手動 自動映射

手動映射配置

xml

 <!-- 復合映射-->
    <resultMap id="baseMap" type="entity.Company">
        <id property="id" column="id"/>
        <result property="companyName" column="company_name" jdbcType="VARCHAR"/>
        <association property="legalPerson" column="id">
            <id property="id" column="id"/>
            <result property="name" column="name"/>
        </association>
    </resultMap>

    <!-- 外部映射-->
    <resultMap id="baseMap1" type="entity.Company">
        <id property="id" column="id"/>
        <result property="companyName" column="company_name" jdbcType="VARCHAR"/>
        <association property="legalPerson" column="id" resultMap="legalPersonMap">
        </association>
    </resultMap>

    <resultMap id="legalPersonMap" type="entity.LegalPerson">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
    </resultMap>
    <!--    嵌套查詢-->
    <resultMap id="baseMap2" type="entity.Company">
        <id property="id" column="id"/>
        <result property="companyName" column="company_name" jdbcType="VARCHAR"/>
        <association property="legalPerson" column="id" select="selectByCompanyId"/>
    </resultMap>

    <select id="selectByCompanyId" resultType="entity.LegalPerson"/>

這個比較基礎,當然也可以使用java代碼,小伙伴自行研究啊,
一個ResultMap 中包含多個ResultMapping 表示一個具體的JAVA屬性到列的映射,其主要值如下:

result id屬性
property類屬性名(必填)
column資料庫列名(必填)
jdbcTypejdbc型別(自動推導)
javaTypejava型別(自動推導)
TypeHandler型別處理器(自動推導)

ResultMapping 有多種表現形式如下:

  1. constructor:構建引數欄位
  2. id:ID欄位
  3. result:普通結構集欄位
  4. association:1對1關聯欄位
  5. collection:1對多集合關聯欄位

上面手動映射圖如下:
在這里插入圖片描述

自動映射配置

    <resultMap id="baseMap" type="entity.Company" autoMapping="true">       
    </resultMap>

自動映射條件

  1. 列名和屬性名同時存在(勿略大小寫)
  2. 當前列未手動設定映射
  3. 屬性類別存在TypeHandler
  4. 開啟autoMapping (默認開啟)

自動映射圖如下:
在這里插入圖片描述

嵌套子查詢

小編用上面的示例寫一個簡單測驗代碼這里用了lombok:
類測驗類以及介面代碼:

public interface CompanyMapper {

     Company selectById(@Param("id")Long id);
}
@Data
public class Company {

    private Long id;

    private String companyName;

    private LegalPerson legalPerson;
    private List<Department> departmentList;

}
@Data
public class LegalPerson {

    private Long id;

    private String name;
    
    private Long companyId;
}

@Data
public class Department {

    private Long id;

    private String departmentName;

    private Long companyId;

    private List<Employee> employeeList;
}
@Test
    public void associationTest() {
        try (SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)) {
            CompanyMapper companyMapper = sqlSession.getMapper(CompanyMapper.class);
            Company company = companyMapper.selectById(1L);
            System.out.println(company.getLegalPerson());
        }
    }

xml配置:

 <resultMap id="CompanyMap" type="entity.Company">
        <id property="id" column="id"/>
        <result property="companyName" column="company_name" jdbcType="VARCHAR"/>
        <association property="legalPerson" column="id" select="selectByCompanyId"/>
        <collection property="departmentList" column="id" select="selectDepartByCompanyId"/>
    </resultMap>

    <resultMap id="LegalPersonMap" type="entity.LegalPerson">
        <id property="id" column="id"/>
        <result property="companyId" column="company_id" />
        <result property="name" column="name"/>
    </resultMap>
    <resultMap id="DepartmentMap" type="entity.Department">
        <id property="id" column="id"/>
        <result property="companyId" column="company_id" />
        <result property="departmentName" column="department_name"/>
    </resultMap>

    <select id="selectByCompanyId" resultMap="LegalPersonMap">
        select * from legal_person where company_id = #{companyId}
    </select>
    <select id="selectDepartByCompanyId" resultMap="DepartmentMap">
        select * from department where company_id = #{companyId}
    </select>
    <select id="selectById" resultMap="CompanyMap"  parameterType="java.lang.Long">
        select * from company where id = #{id}
    </select>

提問:當這里需要傳遞多個引數時該怎么寫?
這里小撰寫偽xml column = “companyId =id,name=companyName” ,然后下面就可以company_id=#{companyId} and name = #{name}

上面小編為什么寫這樣的示例呢?其實小編想說明一個問題,我們來討論一個這樣的問題:假設我們的法人有一家公司,然后公司下面的法人又是自己,那當我們查詢法人的時候要填充公司屬性,但公司填充的時候又查詢到這個法人,那這樣不就死回圈了嗎?那mybatis到底會不會出現死回圈呢

答案:當然不會死回圈了(不信大家自己試一下,小編已經試過了),其實mybatis作者也想到了這個問題,spring中是不是也會有回圈依賴的問題,那mybatis是如何解決的呢,那下面小編繼續講解其中的原理,

回圈依賴

回圈依賴流程圖:
在這里插入圖片描述
根據上面的流程圖,小編帶大家看一下原始碼,大家也可以自己打斷點除錯,在上一篇博客中,封裝行物件的時候用到DefaultResultSetHandler#getRowValue方法,里面用到了applyPropertyMappings方法,因為是嵌套查詢的最侄訓用到getPropertyMappingValue方法:

private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
      
    if (propertyMapping.getNestedQueryId() != null) {
      //是否是嵌套查詢 這個方法就開始和上面流程圖差不多了
      return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
    } else if (propertyMapping.getResultSet() != null) {
      addPendingChildRelation(rs, metaResultObject, propertyMapping);   // TODO is that OK?
      return DEFERRED;
    } else {
       //直接獲取值
      final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
      final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
      return typeHandler.getResult(rs, column);
    }
  }

開始嵌套查詢

private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix)
      throws SQLException {
    //準備引數 ,獲取mappedStatement,動態sql 準備換成key
    final String nestedQueryId = propertyMapping.getNestedQueryId();
    final String property = propertyMapping.getProperty();
    final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
    final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
    final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix);
    Object value = null;
    if (nestedQueryParameterObject != null) {
      final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
      final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql);
      final Class<?> targetType = propertyMapping.getJavaType();
      //是否存在快取
      if (executor.isCached(nestedQuery, key)) {
      	//命中快取 延時加載
        executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
        value = DEFERRED;
      } else {
      	//沒有命中快取 
        final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql);
        if (propertyMapping.isLazy()) {
          //看是否懶加載
          lazyLoader.addLoader(property, metaResultObject, resultLoader);
          value = DEFERRED;
        } else {
         //否則直接查詢
          value = resultLoader.loadResult();
        }
      }
    }
    return value;
  }

延遲加載是在主查詢結束后再將屬性值填充進去,其具體實作在baseExecutor

 @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    //是否可延遲加載
    if (deferredLoad.canLoad()) {
      //可以直接延遲加載
      deferredLoad.load();
    } else {
      //需要延遲加載的放入deferredLoads 這里就是法人下面查找的公司
      deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
    }
  }
public boolean canLoad() {
	 //本地快取有且不是EXECUTION_PLACEHOLDER值,在第一次查詢的時候會有占位符
      return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
    }

    public void load() {
      @SuppressWarnings("unchecked")
      // we suppose we get back a List
      //然后直接從key拿值,然后賦值即可
      List<Object> list = (List<Object>) localCache.getObject(key);
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      resultObject.setValue(property, value);
    }

上面的流程圖和源代碼已經對應起來了,但是大家可能還沒明白mybatis怎么解決回圈依賴的,
首先先說結論,第一使用一級快取,第二個使用延遲加載(這里也間接說明了一級快取是不能關閉的),這邊還用了一個queryStack引數和快取占位符,
下面咱們再次捋一下
1、查詢公司 queryStack=0
2、公司下的法人 queryStack=1
3、法人下的公司 queryStack=2
其中第一次查詢將自己設定快取和第三次查詢時是同一家公司,所以會走一級快取,不會重復查詢資料庫,接著將自己放入到延遲加載集合中,回到主查詢,加載延遲加載中的屬性值即可,

小編完整說明一下回圈依賴的解決:
再次貼一下代碼BaseExecutor代碼:

 @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      clearLocalCache();
    }
    List<E> list;
    try {
     //查詢一次 加一次查詢堆
      queryStack++;
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      //查詢完畢 減一次查詢堆
      queryStack--;
    }
    //當查詢到0的時候就是回傳主查詢,進行延遲裝載
    if (queryStack == 0) {
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      //裝載完延遲后清空延遲加載器
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    //查詢前放入占位符
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
    //查詢并且賦值相應的引數值,會涉及上面的嵌套查詢
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      localCache.removeObject(key);
    }
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }
  1. 第一次查詢公司的時候:BaseExecutor.query方法中 queryStack++,并且設定一級快取占位符;
  2. 在嵌套查詢,即第二次查詢法人的時候,因為不是懶加載則會再次BaseExecutor.query這個方法,queryStack++然后設定查詢法人的一級快取占位符
  3. 查詢法人資訊的時候又查詢公司,即第三次查詢公司有快取了,因為還不能直接加載,則放入了deferredLoads
  4. 回傳結果,先是法人查詢填充屬性完畢queryStack – ,洗掉法人一級快取的占位符,將法人放入一級快取中,之后是公司查詢填充法人資訊完畢queryStack – ,洗掉公司一級快取占位符,將公司放入一級快取中
  5. queryStack為0,執行延遲加載,里面有剛剛放入的一個,并且可以加載了(公司一級快取不為空也不是占位符),延遲加載使用metaObject設定對應的值即可,

總結

今天主要講了映射體系中的手動自動,以及嵌套子查詢和回圈依賴mybatis是如何解決的,還有懶加載和延遲加載是兩回事情,希望小編講得足夠明白了,最近小編在講解程序中留下來一些問題,主要是希望大家不僅僅是看看,更得實踐,否則容易忘記,一起加油啊,接下來小編會講解懶加載,大家繼續關注吧

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/283213.html

標籤:其他

上一篇:架構師成長記_第八周_10_ES-分詞與五種內置分詞器

下一篇:一個自動化專案的思考總結

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more