Mybatis的解析和基本運行原理

Mybatis運行過程

Mybatis的運行過程分爲兩大步:第1步,讀取配置文件緩存到Configuration對象,用於創建SqlSessionFactory;第2步,SqlSession的執行過程。相對而言,SqlSessionFactory的創建還算比較容易理解,而SqlSession的執行過程就不那麼簡單了,它包括許多複雜的技術,要先掌握反射技術和動態代理,這裏主要用到的是JDK動態代理,不熟悉的話請點擊這裏瞭解一下

構建SqlSessionFactory過程

SqlSessionFactory是Mybatis的核心類之一,其最重要的功能就是提供創建MyBatis的核心接口SqlSession,所以首先創建SqlSessionFactory,爲此要提供配置文件和相關的參數。MyBatis是一個複雜的系統,它採用了Builder模式去創建SqlSessionFactory,在實際中課通過SqlSessionFacyoryBuilder去創建,構建分爲兩步:

 1. 通過org.apache.ibatis.builder.xml.XMLConfigBuilder解析配置的XML文件讀取所配置的參數,
   並將讀取的內容存入Configuration類對象中。而Configuration採用的是單例模式,幾乎所有的MyBatis
   配置內容都會存放在這個單例對象中

 2. 使用Configuration對象去創建SqlSessionFactory。MyBatis中的SqlSessionFactory是一個接口,
   而不是一個實現類,爲此MyBatis提供了一個默認的實現類DefaultSqlSessionFactory.在大部分情況下
   沒有必要自己去創建新的實現類。
     這種創建的方式就是一種Builder模式,對於複雜的對象而言,使用構造器參數很難實現,這是使用一個類(比如Configuration)
   作爲統領,一步步構建所需的內容,然後通過它去創建最終的對象(比如SqlSessionFactory),這樣每一步都會很清晰

XMLConfigBuilder解析Xml的源碼:

 private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      settingsElement(root.evalNode("settings"));
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

      從源碼中可以看到,他是通過一步步解析XML內容得到對應的信息,而這些信息正是我們在配置文件中配置的內容,
    這裏只討論typeHandlers解析的方法,其他的類似
      配置的typeHandler都會被註冊到typeHandlerRegister對象中去,它的定義是放在XMLConfiguration的父類
    BaseBuilder中的,代碼如下:

     protected final TypeHandlerRegistry typeHandlerRegistry;
      public BaseBuilder(Configuration configuration) {
        this.configuration = configuration;
        this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
        this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
      }
    可以看出typeHandlerRegistry是Configuration單例的一個屬性,所以可以通過COnfiguration單例拿到typeHandlerRegistry,
    從而進行註冊typeHandler

構建Configuration

在SqlSessionFactory構建中,Configuration是最重要的,它的作用是:

  • 讀入配置文件,包括基礎配置的Xml和映射器XML(或註解)
  • 初始化一些基礎配置,比如Mybatis別名等,一些重要的類對象(如插件、映射器Object工廠等)
  • 提供單例,爲後續的創建SessionFactory服務提供配置的參數
    顯然Configuration不會是一個簡單的類,MyBatis的配置信息都來自於此,首先他會讀出XML配置的信息,然後把它們保存在Configuration單例中,它會做如下初始化:
  • properties全局參數
  • typeAliases別名
  • Plugins 插件
  • objectFactory 對象工廠
  • reflectionFactory 反射工廠
  • settings 環境設置
  • environment 數據庫環境
  • databaseIdProvider 數據庫標識
  • Mappers 映射器

    他們都會以類似typeHandler註冊那樣的方法被存放到Configuration單例,以便將來取出

構建映射器的內部組成

當XMLConfiguration解析XML是會將每一個SQL和其配置的內容保存起來。一般而言,在MyBatis中一條SQL和它相關的配置是有3個部分組成的,它們分別是MappedStatement、SqlSource和BoundSql。

  • MappedStatement的作用保存一個映射器節點(select | insert | delete | update)的內容。它是一個類,包括許多我們配置的SQL、SQL的id、緩存信息、resultMap、parameterType、resultType等重要的配置信息,它還有一個重要的屬性sqlSource。MyB通過它來獲取某條SQL配置的所有信息。
  • SqlSource 是提供BoundSql對象的地方,它是MappedStatement的一個重要屬性。注意,它是一個接口,而不是一個實現類。它有幾個重要的實現類:DynamicSqlSource、ProviderSqlSource、RawSqlSource、StaticSqlSource。它的作用是根據上下文和參數解析生成需要的SQL,這個接口只定義了一個接口方法—–getBoundSql(parameterObject),使用它就可以得到一個BoundSql對象。
  • BoundSql是一個結果對象,也就是SqlSource通過對SQL和參數的解析得到的SQL和參數,它是建立SQL和參數的地方,他有常用的3個屬性:sql、parameterObject、parameterMappings
  • 映射器內部組成圖:
    組成圖
    這裏只是列舉了主要的方法和屬性。MappedStatement對象涉及的東西比較多,一般不去修改它,因爲容易產生不必要的錯誤。SqlSource是一個接口,它的這個主要作用是根據參數和其他的規則組裝SQL,這些都是很複雜的東西,好在MyBatis本身已經實現了它,一般不需要去修改。對於最終的參數和SQL都反映在BoundSql類對象上,在插件中往往需要拿到它進而拿到當前運行的SQL和參數,從而對運行過程做出必要的修改,來滿足特殊的需求,這裏的討論對MyBatis插件的開發是至關重要的
由上圖可知BoundSql會提供3個主要的屬性:parameterMappings,parameterObject和sql
  • parameterObject爲參數本身,可以傳遞簡單對象、POJO、Map或@Param註解的參數,由於它在插件中相當有用,有必要討論它的一些規則
    ①.傳遞簡單對象,包括int、String、float、double等。當傳遞int類型時,MyBatis會把參數變爲Integer對象傳遞,類似的long、String、float、double也是如此。
    ②.傳遞POJO或者Map,parameterObject就是傳入的POJO或者Map
    ③.傳遞多個參數,如果沒有使用@Param註解,那麼MyBatis會把parameterObject變成一個Map< String,Object>對象,其鍵值的關係是按照順序來規劃的,類似於{“1”:p1, “2”:p2, “3”:p3……,”param1”:p1, “param2”:p2, “param3”:p3…}這樣的形式。所以在編寫的時候可以使用#{param1}或者#{1}去引用第一個參數。
    ④.使用@Param註解,MyBatis就會把parameterObject也變成一個Map< String,Object>對象,類似於沒有@Param註解,只是把其數字的鍵值置換成@Param註解鍵值。比如註解@Param(“key1”) String p1、@Param(“key2”) int p2、@Param(“key3”) User p3,那麼parameterObject對象就是一個Map對象,它的鍵值包含{“key1”:p1,”key2”:p2, “key3”:p3,”param1”:p1, “param2”:p2,”param3”:p3}

  • parameterMappings是一個List,它的每一個元素都是ParameterMapping對象。對象會描述參數,參數包括屬性名稱、表達式、javaType、jdbcType、typeHandler等重要信息,一般不需要去改變他,通過它就可以實現參數和SQL的結合,以便於PreparedStatement能夠通過它找到paramterObject對象的屬性設置參數,使得程序能準確運行。

  • sql屬性就是書寫在映射器裏面的一條被SqlSource解析後的SQL,大部分情況下無需修改它,只是在使用插件是可以根據需求進行改寫。

    構建SqlSessionFactory

    有了Configuration對象,構建SqlSessionFactory是很簡單的。

 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

這裏寫圖片描述
由圖上的源碼和之前的分析,可以得知MyBatis會根據文件流先生成Configuration對象,進而構建SqlSessionFactory對象,真正的難點在於構建Configuration對象,所以關注的重心實際應該是Configuration對象,而不是SqlSessionFactory對象。

SqlSession運行過程

映射器(Mapper)的動態代理

在代碼中常看到這樣一句代碼:

UserMapper userMapper = SqlSession.getMapper(UserMapper.class); 

查看Mybatis源碼如何實現getMapper方法的:
實現方法
由源碼可以得知,它運用了Configuration的對象的getMapper方法,繼續往下看:
cfg_mapper
顯然,它有用到了Mapperregistry來獲取對應的接口對象,源碼如下:

首先它判斷是否註冊一個Mapper,沒有的話則拋出異常,如果有,就會啓用MapperProxyFactory工廠來生成一個代理對象,繼續追蹤源碼:
這裏寫圖片描述
注意紅圈的代碼,Mapper映射是通過動態代理來實現的,這裏可以看到動態代理對接口的綁定,它的作用就是生成動態代理對象,而代理的方法則是放到了MapperProxy類中,因此再看一下MapperProxy的源碼:

可以看到這裏的invoke邏輯,如果Mapper是一個動態代理對象,那麼它就會運行到invoke方法裏面,invoke方法首先判斷是否是一個類,這裏的Mapper是一個接口,所以判斷失敗。然後會生成MapperMethod對象,它是通過cachedMapperMethod方法對其初始化,最後執行execute方法,把SqlSession和當前的參數傳遞進去。execute方法的源碼如下:
execute這裏寫圖片描述
這裏不需要全部明白所有方法的含義,只要討論executeForMany方法的實現即可。通過源碼我們可以知道,實際上它最後就是通過SqlSession對象去運行SQL,其他的增刪改查也是類似這樣處理的。
所以,現在就清楚MyBatis爲什麼只用Mapper接口便能夠運行起來了,因爲Mapper的XML文件的命名空間對應的是這個接口的全限定名,而方法就是那條SQL的id,這樣MyBatis就可以根據全路徑和方法名,將其和代理對象綁定起來,通過動態代理技術,讓這個接口運行起來,而後採用命令模式。最後使用SQlSession接口的方法使得它能夠執行對應的SQL,只是有了這層封裝,就可以採用接口編程,這樣的編程更爲簡單明瞭。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章