Mybatis源碼解析 - mapper代理對象的生成,你有想過嗎

前言

  開心一刻html

    本人幼教老師,冬天戴帽子進教室,被小朋友看到,這時候,有個小傢伙對我說:老師你的帽子太醜,趕忙摘了吧。我逗他:那你好好學習,之後給老師買個漂亮的?這孩子想都沒想馬上回答:等我賺錢了,帶你去韓國整形git

簡單示例

  咱們先來看一個純粹的mybatis示例(不集成spring等其餘框架),代碼很簡單,結構以下spring

  完整代碼地址:mybatis;mapper層和咱們平時說的dao層指的是同一個內容,都是數據庫操做的封裝,可是在沒有集成mybatis時,dao層的接口都是須要咱們手動去寫其實現類,可在上圖中咱們卻發現:咱們並無手動去實現PersonMapper接口,但工程卻能實實在在的查詢數據庫,獲取咱們須要的數據,以下圖所示sql

  從上圖咱們發現,PersonMapper實例是一個代理對象,咱們操做的實際上是PersonMapper的代理實現;也就是說不用咱們手動去實現PersonMapper接口,mybatis會動態生成PersonMapper的代理實例,而後由代理實例完成數據庫的操做數據庫

  那麼問題來了,mybatis是什麼時候、何地、如何生成mapper代理實例的呢?咱們接着往下看設計模式

源碼分析

  針對上述問題,咱們來跟下mybatis源碼springboot

  SqlSessionFactory的建立

    XMLConfigBuilder解析Mybatis配置文件(mybatis-config.xml),將配置文件中各個屬性解析到Configuration實例中,而後以Configuration實例構建SqlSessionFactory(實際是DefaultSqlSessionFactory);其中parseConfiguration方法是解析的具體過程,有興趣的能夠更深一步的去探究mybatis

/**
 * root是以configuration標籤開始的文檔樹
 * 解析配置文件中的各個標籤,並存放到Configuration實例對應的屬性中
 * 解析完成以後,配置文件中的內容所有解析到了Configuration實例中
 * @param root
 */
private void parseConfiguration(XNode root) {
    try {
        //issue #117 read properties first
        propertiesElement(root.evalNode("properties"));                                // 解析配置文件中的properties標籤
        Properties settings = settingsAsProperties(root.evalNode("settings"));        // 解析配置文件中的settings標籤
        loadCustomVfs(settings);
        typeAliasesElement(root.evalNode("typeAliases"));                            // 解析配置文件中的typeAliases標籤
        pluginElement(root.evalNode("plugins"));                                    // 解析配置文件中的plugins標籤
        objectFactoryElement(root.evalNode("objectFactory"));                        // 解析配置文件中的objectFactory標籤
        objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));            // 解析配置文件中的objectWrapperFactory標籤
        reflectorFactoryElement(root.evalNode("reflectorFactory"));                    // 解析配置文件中的reflectorFactory標籤
        settingsElement(settings);
        // read it after objectFactory and objectWrapperFactory issue #631            
        environmentsElement(root.evalNode("environments"));                            // 解析配置文件中的environments標籤
        databaseIdProviderElement(root.evalNode("databaseIdProvider"));                // 解析配置文件中的databaseIdProvider標籤
        typeHandlerElement(root.evalNode("typeHandlers"));                            // 解析配置文件中的typeHandlers標籤
        mapperElement(root.evalNode("mappers"));                                    // 解析配置文件中的mappers標籤
    } catch (Exception e) {
        throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}
View Code

    上述代碼中的mapperElement(root.evalNode("mappers"));是否是很誘人?與咱們的mapper有關係,是否是在這裏就生成了mapper的代理實例,仍是隻是讀取了mapper配置文件的內容?暫時還不敢確定,那麼咱們跟進去看看app

    其中有兩個方法值得重點關注下,具體以下,裏面的註釋能夠重點看下,有興趣的能夠更進一步的跟進去框架

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));        // 解析映射文件Person.xml
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();    // 將mapper與namespace綁定起來; 將PersonMapper接口與MapperProxyFactory關聯起來
    }

    parsePendingResultMaps();    // 解析Configuration的incompleteResultMaps到Configuration的resultMaps
    parsePendingCacheRefs();    // 解析Configuration的incompleteCacheRefs到Configuration的cacheRefMap
    parsePendingStatements();    // 解析Configuration的incompleteStatements到Configuration的mappedStatements
}

/**
 * context是映射文件:Person.xml的文檔樹,以mapper標籤開始
 * 解析映射文件中的各個標籤,並存放到MapperBuilderAssistant實例對應的屬性中
 */
private void configurationElement(XNode context) {
    try {
      String namespace = context.getStringAttribute("namespace");            // 解析mapper標籤的namespace屬性
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);    // namespace屬性值解析到Configuration的mapperRegistry中
      cacheRefElement(context.evalNode("cache-ref"));    // 解析cache-ref標籤到Configuration的cacheRefMap中
      cacheElement(context.evalNode("cache"));            // 解析cache標籤到Configuration的caches中
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));    // 解析parameterMap標籤到Configuration的parameterMaps中
      resultMapElements(context.evalNodes("/mapper/resultMap"));        // 解析resultMap標籤到Configuration的resultMaps中
      sqlElement(context.evalNodes("/mapper/sql"));                        // 解析sql標籤到XMLMapperBuilder的sqlFragments中
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));    // 解析select|insert|update|delete標籤到Configuration的mappedStatements中
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
}
View Code

    此時SqlSessionFactory已經建立,但PersonMapper的代理實例尚未建立;期間準備了不少東西,包括讀取配置文件和映射文件的內容,並將其放置到Configuration實例的對應屬性中

  SqlSession的建立

    實例化了Transaction(JdbcTransaction)、Executor(SimpleExecutor)和SqlSession(DefaultSqlSession),此時mapper代理實例仍未被建立

  Mapper代理對象的建立

    能夠看到,最終仍是利用了JDK的動態代理

protected T newInstance(MapperProxy<T> mapperProxy) {
    // 利用JDK的動態代理生成mapper的代理實例
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

    生成了mapper的代理實例,後續就能夠利用此代理實例進行數據庫的操做了

    更多動態代理的信息請查看:設計模式之代理,手動實現動態代理,揭祕原理實現

總結

  一、咱們用mytabis操做數據庫,有一個固定流程:先建立SqlSessionFactory,而後建立SqlSession,而後再建立獲取mapper代理對象,最後利用mapper代理對象完成數據庫的操做;一次數據庫操做完成後須要關閉SqlSession;

  二、建立SqlSessionFactory實例的過程當中,解析mybatis配置文件和映射文件,將內容都存放到Configuration實例的對應屬性中;建立SqlSession的過程當中,有建立事務Transaction、執行器Executor,以及DefaultSqlSession;Mapper代理對象的建立,利用的是JDK的動態代理,InvocationHandler是MapperProxy,後續Mapper代理對象方法的執行都會先通過MapperProxy的invoke方法;

  三、不少細節沒有講到,但大致流程就是這樣;另外提下,實際應用中,mybatis每每不會單獨使用,絕大多數都是集成在spring中;關於在spring的集成下,mapper代理對象的建立過程查看:springboot集成下,mybatis的mapper代理對象到底是如何生成的

相關文章
相關標籤/搜索