1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

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

        共 18721字,需瀏覽 38分鐘

         ·

        2022-08-03 06:04

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

        你來,我們一起精進(jìn)!你不來,我和你的競爭對手一起精進(jìn)!

        編輯:業(yè)余草

        blog.csdn.net/b379685397

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

        一、Mybatis運(yùn)行流程概述

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

        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.獲取對應(yīng)mapper
                TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

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

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

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

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

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

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

        二、配置加載的核心類

        建造器三個(gè)核心類

        在 MyBatis 中負(fù)責(zé)加載配置文件的核心類有三個(gè),類圖如下:

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

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

        XMLConfigBuilder

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

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

        三、建造者模式

        什么是建造者模式

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

        建造者模式類圖如下:

        建造者模式

        各要素如下:

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

        應(yīng)用舉例:紅包的創(chuàng)建是個(gè)復(fù)雜的過程,可以使用構(gòu)建者模式進(jìn)行創(chuàng)建

        代碼示例:

        1、紅包對象RedPacket

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

            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、構(gòu)建對象

        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:流式編程風(fēng)格越來越流行,如 zookeeper 的 Curator、JDK8 的流式編程等等都是例子。流式編程的優(yōu)點(diǎn)在于代碼編程性更高、可讀性更好,缺點(diǎn)在于對程序員編碼要求更高、不太利于調(diào)試。建造者模式是實(shí)現(xiàn)流式編程風(fēng)格的一種方式;

        與工廠模式區(qū)別

        建造者模式應(yīng)用場景如下:

        • 需要生成的對象具有復(fù)雜的內(nèi)部結(jié)構(gòu),實(shí)例化對象時(shí)要屏蔽掉對象代碼與復(fù)雜對象的實(shí)例化過程解耦,可以使用建造者模式;簡而言之,如果“遇到多個(gè)構(gòu)造器參數(shù)時(shí)要考慮用構(gòu)建器”;
        • 對象的實(shí)例化是依賴各個(gè)組件的產(chǎn)生以及裝配順序,關(guān)注的是一步一步地組裝出目標(biāo)對
        • 象,可以使用建造器模式;

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





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

        四、Configuration 對象介紹

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

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

        Configuration對象圖解:

        Configuration對象圖解

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

        XMLConfigBuilder.XMLConfigBuilder

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

        五、配置加載流程解析

        配置加載過程

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

        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.
              }
            }
        }

        第二步:進(jìn)入 XMLConfigBuilder 的 parseConfiguration 方法,對 MyBatis 核心配置文件的各個(gè)元素進(jìn)行解析,讀取元素信息后填充到 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é)點(diǎn)
              propertiesElement(root.evalNode("properties"));
              //解析<settings>節(jié)點(diǎn)
              Properties settings = settingsAsProperties(root.evalNode("settings"));
              loadCustomVfs(settings);
              //解析<typeAliases>節(jié)點(diǎn)
              typeAliasesElement(root.evalNode("typeAliases"));
              //解析<plugins>節(jié)點(diǎn)
              pluginElement(root.evalNode("plugins"));
              //解析<objectFactory>節(jié)點(diǎn)
              objectFactoryElement(root.evalNode("objectFactory"));
              //解析<objectWrapperFactory>節(jié)點(diǎn)
              objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
              //解析<reflectorFactory>節(jié)點(diǎn)
              reflectorFactoryElement(root.evalNode("reflectorFactory"));
              settingsElement(settings);//將settings填充到configuration
              // read it after objectFactory and objectWrapperFactory issue #631
              //解析<environments>節(jié)點(diǎn)
              environmentsElement(root.evalNode("environments"));
              //解析<databaseIdProvider>節(jié)點(diǎn)
              databaseIdProviderElement(root.evalNode("databaseIdProvider"));
              //解析<typeHandlers>節(jié)點(diǎn)
              typeHandlerElement(root.evalNode("typeHandlers"));
              //解析<mappers>節(jié)點(diǎn)
              mapperElement(root.evalNode("mappers"));
            } catch (Exception e) {
              throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
            }

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

        private void configurationElement(XNode context) {
            try {
             //獲取mapper節(jié)點(diǎn)的namespace屬性
              String namespace = context.getStringAttribute("namespace");
              if (namespace == null || namespace.equals("")) {
                throw new BuilderException("Mapper's namespace cannot be empty");
              }
              //設(shè)置builderAssistant的namespace屬性
              builderAssistant.setCurrentNamespace(namespace);
              //解析cache-ref節(jié)點(diǎn)
              cacheRefElement(context.evalNode("cache-ref"));
              //重點(diǎn)分析 :解析cache節(jié)點(diǎn)----------------1-------------------
              cacheElement(context.evalNode("cache"));
              //解析parameterMap節(jié)點(diǎn)(已廢棄)
              parameterMapElement(context.evalNodes("/mapper/parameterMap"));
              //重點(diǎn)分析 :解析resultMap節(jié)點(diǎn)----------------2-------------------
              resultMapElements(context.evalNodes("/mapper/resultMap"));
              //解析sql節(jié)點(diǎn)
              sqlElement(context.evalNodes("/mapper/sql"));
              //重點(diǎn)分析 :解析select、insert、update、delete節(jié)點(diǎn) ----------------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 解析過程中,有四個(gè)點(diǎn)需要注意:

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

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

        XMLStatmentBuilder

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

        Mybatis的配置加載流程圖

        瀏覽 42
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評論
        圖片
        表情
        推薦
        點(diǎn)贊
        評論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            另类小说亚洲区欧美动图无码第页 | 亚洲精品字幕 | h高潮嗯啊娇喘抽搐np公车古代 | 超碰97人操 | 国模一区二区三区 | 成人免费无码区色情免费看 | 婷婷五月狠狠 | 国产精品美女裸体免费看 | 特级特黄AAAAAAAA片 | 91成人一区二区三区 |