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


前言

  開心一刻

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

簡單示例

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

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

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

  那么問題來了,mybatis是何時、何地、如何生成mapper代理實例的呢?我們接着往下看

源碼分析

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

  SqlSessionFactory的創建

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

/**
 * 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配置文件的內容?暫時還不敢肯定,那么我們跟進去看看

    其中有兩個方法值得重點關注下,具體如下,里面的注釋可以重點看下,有興趣的可以更進一步的跟進去

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的代理實例,后續就可以利用此代理實例進行數據庫的操作了

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

總結

  1、我們用mytabis操作數據庫,有一個固定流程:先創建SqlSessionFactory,然后創建SqlSession,然后再創建獲取mapper代理對象,最后利用mapper代理對象完成數據庫的操作;一次數據庫操作完成后需要關閉SqlSession;

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM