<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          圖文并茂解析Mybatis配置加載過程!

          共 18721字,需瀏覽 38分鐘

           ·

          2022-08-03 06:04

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來,我們一起精進!你不來,我和你的競爭對手一起精進!

          編輯:業(yè)余草

          blog.csdn.net/b379685397

          推薦:https://www.xttblog.com/?p=5353

          一、Mybatis運行流程概述

          為了熟悉Mybatis的運行流程,我們先看一段代碼。

          public class MybatisDemo {
              private SqlSessionFactory sqlSessionFactory;

              @Before
              public void init() throws IOException {
                      //--------------------第一步:加載配置---------------------------
                  // 1.讀取mybatis配置文件創(chuàng)SqlSessionFactory
                      String resource = "mybatis-config.xml";
                      InputStream inputStream = Resources.getResourceAsStream(resource);
                      // 1.讀取mybatis配置文件創(chuàng)SqlSessionFactory
                      sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
                      inputStream.close();
              }

              @Test
              // 快速入門
              public void quickStart() throws IOException {
                  //--------------------第二部,創(chuàng)建代理對象---------------------------
                  // 2.獲取sqlSession 
                  SqlSession sqlSession = sqlSessionFactory.openSession();
                  // 3.獲取對應mapper
                  TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

                  //--------------------第三步:獲取數據---------------------------
                  // 4.執(zhí)行查詢語句并返回單條數據
                  TUser user = mapper.selectByPrimaryKey(2);
                  System.out.println(user);

                  System.out.println("----------------------------------");

                  // 5.執(zhí)行查詢語句并返回多條數據
          //  List<TUser> users = mapper.selectAll();
          //  for (TUser tUser : users) {
          //   System.out.println(tUser);
          //  }
              }
          }

          以上是我們一個使用mybatis訪問數據的demo,通過對快速入門代碼的分析,可以把 MyBatis 的運行流程分為三大階段:

          1. 「初始化階段」:讀取 XML 配置文件和注解中的配置信息,創(chuàng)建配置對象,并完成各個模塊的初始化的工作;
          2. 「代理封裝階段」:封裝 iBatis 的編程模型,使用 mapper 接口開發(fā)的初始化工作;
          3. 「數據訪問階段」:通過 SqlSession 完成 SQL 的解析,參數的映射、SQL 的執(zhí)行、結果的解析過程;

          今天我們就介紹以下第一個階段中,Mybatis是如何讀取配置的

          二、配置加載的核心類

          建造器三個核心類

          在 MyBatis 中負責加載配置文件的核心類有三個,類圖如下:

          MyBatis加載配置文件核心類
          • BaseBuilder:所有解析器的父類,包含配置文件實例,為解析文件提供的一些通用的方法;
          • XMLConfigBuilder:主要負責解析 mybatis-config.xml;
          • XMLMapperBuilder:主要負責解析映射配置 Mapper.xml 文件;
          • XMLStatementBuilder:主要負責解析映射配置文件中的 SQL 節(jié)點;

          XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder 這三個類在配置文件加載過程中非常重要,具體分工如下圖所示:

          XMLConfigBuilder

          這三個類使用了建造者模式對 configuration 對象進行初始化,但是沒有使用建造者模式
          的“肉體”(流式編程風格),只用了靈魂(屏蔽復雜對象的創(chuàng)建過程),把建造者模式演繹
          成了工廠模式;后面還會對這三個類源碼進行分析;

          居然這三個對象使用的是建造者模式,那么我們稍后介紹下什么是建造者模式

          三、建造者模式

          什么是建造者模式

          建造者模式(BuilderPattern)使用多個簡單的對象一步一步構建成一個復雜的對象。這種類型的設計模式屬于創(chuàng)建型模式,它提供了一種創(chuàng)建對象的最佳方式。

          建造者模式類圖如下:

          建造者模式

          各要素如下:

          • 「Product」:要創(chuàng)建的復雜對象
          • 「Builder」:給出一個抽象接口,以規(guī)范產品對象的各個組成成分的建造。這個接口規(guī)定要實現復雜對象的哪些部分的創(chuàng)建,并不涉及具體的對象部件的創(chuàng)建;
          • 「ConcreteBuilder」:實現 Builder 接口,針對不同的商業(yè)邏輯,具體化復雜對象的各部分的創(chuàng)建。在建造過程完成后,提供產品的實例;
          • 「Director」:調用具體建造者來創(chuàng)建復雜對象的各個部分,在指導者中不涉及具體產品的信息,只負責保證對象各部分完整創(chuàng)建或按某種順序創(chuàng)建;

          應用舉例:紅包的創(chuàng)建是個復雜的過程,可以使用構建者模式進行創(chuàng)建

          代碼示例:

          1、紅包對象RedPacket

          public class RedPacket {
              private String publisherName; //發(fā)包人
              private String acceptName; //收包人
              private BigDecimal packetAmount; //紅包金額
              private int packetType; //紅包類型
              private Date pulishPacketTime; //發(fā)包時間
              private Date openPacketTime; //搶包時間

              public RedPacket(String publisherName, String acceptName, BigDecimal packetAmount, int packetType, Date pulishPacketTime, Date openPacketTime) {
                  this.publisherName = publisherName;
                  this.acceptName = acceptName;
                  this.packetAmount = packetAmount;
                  this.packetType = packetType;
                  this.pulishPacketTime = pulishPacketTime;
                  this.openPacketTime = openPacketTime;
              }

              public String getPublisherName() {
                      return publisherName;
              }

              public void setPublisherName(String publisherName) {
                      this.publisherName = publisherName;
              }

              public String getAcceptName() {
                      return acceptName;
              }

              public void setAcceptName(String acceptName) {
                      this.acceptName = acceptName;
              }

              public BigDecimal getPacketAmount() {
                      return packetAmount;
              }

              public void setPacketAmount(BigDecimal packetAmount) {
                      this.packetAmount = packetAmount;
              }

              public int getPacketType() {
                      return packetType;
              }

              public void setPacketType(int packetType) {
                      this.packetType = packetType;
              }

              public Date getPulishPacketTime() {
                      return pulishPacketTime;
              }

              public void setPulishPacketTime(Date pulishPacketTime) {
                      this.pulishPacketTime = pulishPacketTime;
              }

              public Date getOpenPacketTime() {
                      return openPacketTime;
              }

              public void setOpenPacketTime(Date openPacketTime) {
                      this.openPacketTime = openPacketTime;
              }

              @Override
              public String toString() {
                      return "RedPacket [publisherName=" + publisherName + ", acceptName="
                                      + acceptName + ", packetAmount=" + packetAmount
                                      + ", packetType=" + packetType + ", pulishPacketTime="
                                      + pulishPacketTime + ", openPacketTime=" + openPacketTime + "]";
              }
          }

          2、構建對象

          public class Director {
              public static void main(String[] args) {
                  RedPacket redPacket = RedPacketBuilderImpl.getBulider()
                      .setPublisherName("DK").setAcceptName("粉絲")
                      .setPacketAmount(new BigDecimal("888")).setPacketType(1)                       .setOpenPacketTime(new Date())
                      .setPulishPacketTime(new Date()).build();
                  System.out.println(redPacket);
              }
          }

          PS:流式編程風格越來越流行,如 zookeeper 的 Curator、JDK8 的流式編程等等都是例子。流式編程的優(yōu)點在于代碼編程性更高、可讀性更好,缺點在于對程序員編碼要求更高、不太利于調試。建造者模式是實現流式編程風格的一種方式;

          與工廠模式區(qū)別

          建造者模式應用場景如下:

          • 需要生成的對象具有復雜的內部結構,實例化對象時要屏蔽掉對象代碼與復雜對象的實例化過程解耦,可以使用建造者模式;簡而言之,如果“遇到多個構造器參數時要考慮用構建器”;
          • 對象的實例化是依賴各個組件的產生以及裝配順序,關注的是一步一步地組裝出目標對
          • 象,可以使用建造器模式;

          建造者模式與工程模式的區(qū)別在于:





          「設計模式」「形象比喻」「對象復雜度」「客戶端參與程度」
          工廠模式生產大眾版關注的是一個產品整體,無須關心產品的各部分是如何創(chuàng)建出來的;客戶端對產品的創(chuàng)建過程參與度低,對象實例化時屬性值相對比較固定;
          建造者模式生產定制版建造的對象更加復雜,是一個復合產品,它由各個部件復合而成,部件不同產品對象不同,生成的產品粒度細;客戶端參與了產品的創(chuàng)建,決定了產品的類型和內容,參與度高;適合實例化對象時屬性變化頻繁的場景;

          四、Configuration 對象介紹

          實例化并初始化 Configuration 對象是第一個階段的最終目的,所以熟悉 configuration 對
          象是理解第一個階段代碼的核心;configuration 對象的關鍵屬性解析如下:

          • MapperRegistry:mapper 接口動態(tài)代理工廠類的注冊中心。在 MyBatis 中,通過mapperProxy 實現 InvocationHandler 接口,MapperProxyFactory 用于生成動態(tài)代理的實例對象;
          • ResultMap:用于解析 mapper.xml 文件中的 resultMap 節(jié)點,使用 ResultMapping 來封裝id,result 等子元素;
          • MappedStatement:用于存儲 mapper.xml 文件中的 select、insert、update 和 delete 節(jié)點,同時還包含了這些節(jié)點的很多重要屬性;
          • SqlSource:用于創(chuàng)建 BoundSql,mapper.xml 文件中的 sql 語句會被解析成 BoundSql 對象,經過解析 BoundSql 包含的語句最終僅僅包含?占位符,可以直接提交給數據庫執(zhí)行;

          Configuration對象圖解:

          Configuration對象圖解

          需要特別注意的是 Configuration 對象在 MyBatis 中是單例的,生命周期是應用級的,換句話說只要 MyBatis 運行 Configuration 對象就會獨一無二的存在;在 MyBatis 中僅在
          org.apache.ibatis.builder.xml.XMLConfigBuilder.XMLConfigBuilder(XPathParser, String, Properties)中有實例化 configuration 對象的代碼,如下圖:

          XMLConfigBuilder.XMLConfigBuilder

          Configuration 對象的初始化(屬性復制),是在建造 SqlSessionfactory 的過程中進行的,接下
          來分析第一個階段的內部流程;

          五、配置加載流程解析

          配置加載過程

          可以把第一個階段配置加載過程分解為四個步驟,四個步驟如下圖:

          MyBatis配置加載過程

          第一步:通過 SqlSessionFactoryBuilder 建造 SqlSessionFactory,并創(chuàng)建 XMLConfigBuilder 對象讀取 MyBatis 核心配置文件 , 見源碼方法 :
          org.apache.ibatis.session.SqlSessionFactoryBuilder.build(Reader, String, Properties)

          public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
              try {
                //讀取配置文件
                XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
                return build(parser.parse());//解析配置文件得到configuration對象,并返回SqlSessionFactory
              } catch (Exception e) {
                throw ExceptionFactory.wrapException("Error building SqlSession.", e);
              } finally {
                ErrorContext.instance().reset();
                try {
                  reader.close();
                } catch (IOException e) {
                  // Intentionally ignore. Prefer previous error.
                }
              }
          }

          第二步:進入 XMLConfigBuilder 的 parseConfiguration 方法,對 MyBatis 核心配置文件的各個元素進行解析,讀取元素信息后填充到 configuration 對象。在 XMLConfigBuilder 的 mapperElement()方法中通過 XMLMapperBuilder 讀取所有 mapper.xml 文件;見方法:
          org.apache.ibatis.builder.xml.XMLConfigBuilder.parseConfiguration(XNode);

          public Configuration parse() {
              if (parsed) {
                throw new BuilderException("Each XMLConfigBuilder can only be used once.");
              }
              parsed = true;
              parseConfiguration(parser.evalNode("/configuration"));
              return configuration;
            }

            private void parseConfiguration(XNode root) {
              try {
                //issue #117 read properties first
               //解析<properties>節(jié)點
                propertiesElement(root.evalNode("properties"));
                //解析<settings>節(jié)點
                Properties settings = settingsAsProperties(root.evalNode("settings"));
                loadCustomVfs(settings);
                //解析<typeAliases>節(jié)點
                typeAliasesElement(root.evalNode("typeAliases"));
                //解析<plugins>節(jié)點
                pluginElement(root.evalNode("plugins"));
                //解析<objectFactory>節(jié)點
                objectFactoryElement(root.evalNode("objectFactory"));
                //解析<objectWrapperFactory>節(jié)點
                objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
                //解析<reflectorFactory>節(jié)點
                reflectorFactoryElement(root.evalNode("reflectorFactory"));
                settingsElement(settings);//將settings填充到configuration
                // read it after objectFactory and objectWrapperFactory issue #631
                //解析<environments>節(jié)點
                environmentsElement(root.evalNode("environments"));
                //解析<databaseIdProvider>節(jié)點
                databaseIdProviderElement(root.evalNode("databaseIdProvider"));
                //解析<typeHandlers>節(jié)點
                typeHandlerElement(root.evalNode("typeHandlers"));
                //解析<mappers>節(jié)點
                mapperElement(root.evalNode("mappers"));
              } catch (Exception e) {
                throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
              }

          第三步:XMLMapperBuilder 的核心方法為 configurationElement(XNode),該方法對 mapper.xml 配置文件的各個元素進行解析,讀取元素信息后填充到 configuration 對象。

          private void configurationElement(XNode context) {
              try {
               //獲取mapper節(jié)點的namespace屬性
                String namespace = context.getStringAttribute("namespace");
                if (namespace == null || namespace.equals("")) {
                  throw new BuilderException("Mapper's namespace cannot be empty");
                }
                //設置builderAssistant的namespace屬性
                builderAssistant.setCurrentNamespace(namespace);
                //解析cache-ref節(jié)點
                cacheRefElement(context.evalNode("cache-ref"));
                //重點分析 :解析cache節(jié)點----------------1-------------------
                cacheElement(context.evalNode("cache"));
                //解析parameterMap節(jié)點(已廢棄)
                parameterMapElement(context.evalNodes("/mapper/parameterMap"));
                //重點分析 :解析resultMap節(jié)點----------------2-------------------
                resultMapElements(context.evalNodes("/mapper/resultMap"));
                //解析sql節(jié)點
                sqlElement(context.evalNodes("/mapper/sql"));
                //重點分析 :解析select、insert、update、delete節(jié)點 ----------------3-------------------
                buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
              } catch (Exception e) {
                throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
              }
            }

          在 XMLMapperBuilder 解析過程中,有四個點需要注意:

          1. resultMapElements(List)方法用于解析 resultMap 節(jié)點,這個方法非常重要, 一定要跟源碼理解;解析完之后數據保存在 configuration 對象的 resultMaps 屬性中;如下圖
          XMLMapperBuilder 解析過程
          1. 2XMLMapperBuilder 中在實例化二級緩存(見 cacheElement(XNode))、實例化 resultMap (見 resultMapElements(List))過程中都使用了建造者模式,而且是建造者模 式的典型應用;
          2. XMLMapperBuilder 和 XMLMapperStatmentBuilder 有 自 己 的 “ 秘 書 ” MapperBuilderAssistant。XMLMapperBuilder 和 XMLMapperStatmentBuilder 負責解析 讀取配置文件里面的信息,MapperBuilderAssistant 負責將信息填充到 configuration。將文件解析和數據的填充的工作分離在不同的類中,符合單一職責原則;
          3. 在 buildStatementFromContext(List)方法中,創(chuàng)建 XMLStatmentBuilder 解析 Mapper.xml 中 select、insert、update、delete 節(jié)點

          第四步:在 XMLStatmentBuilder 的 parseStatementNode()方法中,對 Mapper.xml 中 select、 insert、update、delete 節(jié)點進行解析,并調用 MapperBuilderAssistant 負責將信息填充到 configuration。在理解 parseStatementNod()方法之前,有必要了解 MappedStatement,這個類用于封裝 select、insert、update、delete 節(jié)點的信息;如下圖所示:

          XMLStatmentBuilder

          至此,整個Mybatis的配置即加載完畢,整個加載流程圖如下:

          Mybatis的配置加載流程圖

          瀏覽 42
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  成人精品人妻一区二区三区 | 狼友资源网 | 插逼视频国产 | 老熟妇久久久XXX预见频 | 999精品视频在线观看 |