成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

MyBatis 源碼解析(一):初始化和動態(tài)代理

娣辯孩 / 3146人閱讀

摘要:最終解析出的和依然是設(shè)置到中。到這里,初始化部分就結(jié)束了。總結(jié)的初始化流程主要是解析配置文件,將相關(guān)信息保存在中,同時對每個代表的生成代理對象工廠。

簡介

MyBatis 是 Java 開發(fā)中非常流行的 ORM 框架,其封裝了 JDBC 并且解決了 Java 對象與輸入?yún)?shù)和結(jié)果集的映射,同時又能夠讓用戶方便地手寫 SQL 語句。MyBatis 的行為類似于以下幾行代碼:

Class.forName("com.mysql.jdbc.Driver");
Connection conn = DriverManager.getConnection(url, usr, password);
PraparedStatement st = conn.prepareStatement(sql);
st.setInt(0, 1);
st.execute();
ResultSet rs = st.getResultSet();
while (rs.next()) {
    String result = rs.getString(colname);
}

上面是 JDBC 的使用流程,MyBatis 其實就是對上面的代碼進行分解包裝。本文將對 MyBatis 的代碼進行分析,探究其中的邏輯。

基本用法

首先從 MyBatis 的基本用法開始,下面是 MyBatis 官網(wǎng)的入門示例:

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

其中 mabatis-config.xml 是 MyBatis 的核心配置文件,其中包括數(shù)據(jù)源、事務(wù)管理器、別名以及 SQL 對應(yīng)的 Mapper 文件等,如下所示:




  
    
      
      
        
        
        
        
      
    
  
  
    
  

有了 SqlSessionFactory 后就可以創(chuàng)建 SqlSession 來調(diào)用 select 以及 update 等方法請求數(shù)據(jù)了:

try {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
} finally {
  session.close();
}
配置文件解析

我們按照上面的代碼流程開始分析源碼,首先是配置文件的解析:SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream) ,SqlSessionFactoryBuilder 顯然是為了構(gòu)建 SqlSessionFactory,而且是從配置文件的輸入流構(gòu)建,代碼如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 創(chuàng)建 XMLConfigBuilder
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // parse.parse() 進行解析
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

首先是創(chuàng)建了一個 XMLConfigBuilder 對象,它是用來解析 Config 文件的。XMLConfigBuilder 繼承自 BaseBuilderBaseBuilder 中有個 Configuration 類型的變量,這個類需要重點關(guān)注,Config 文件中解析出來的所有信息都保存在這個變量中。

創(chuàng)建了 XMLConfigBuilder 后調(diào)用了其 parse 方法:

  public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 在這個函數(shù)中解析
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

這里主要邏輯在 parseConfiguration 中:

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

這里解析了 config 文件中所有的標簽,包括 properties、settings 以及 mappers 等,下面挑幾個看一下。

settings

settings 是對 MyBatis 的一些配置項,包括緩存的開啟以及是否使用駝峰轉(zhuǎn)換(mapUnderscoreToCamelCase)等,代碼如下:

private void settingsElement(XNode context) throws Exception {
   if (context != null) {
   // 將配置項保存到 Properties 中
     Properties props = context.getChildrenAsProperties();
     // Check that all settings are known to the configuration class
     MetaClass metaConfig = MetaClass.forClass(Configuration.class);
     for (Object key : props.keySet()) {
       if (!metaConfig.hasSetter(String.valueOf(key))) {
         throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
       }
     }
     configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
     // 默認開啟緩存
     configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
     configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));      
     configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
     configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
     configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
     configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
     configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
     configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
     configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
     configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
     configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
     configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
     configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
     configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
     configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
     configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
     configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
     configuration.setLogPrefix(props.getProperty("logPrefix"));
     configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
   }
 }

可以看出,settings 的子節(jié)點保存在 Properties 中,然后校驗是否有不合法的子節(jié)點,最后提取出其中的屬性保存到 Configuration 中,上面提到這個類專門用于保存 Config 文件解析出的信息。

從上面也可以看到 MyBatis 的一些默認屬性,例如一級緩存如果沒有配置,那么默認是開啟的。

environments

environments 包含了數(shù)據(jù)源(dataSource) 和事務(wù)管理器(transactionManager) 的配置,代碼如下:

private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
          // 解析 transactionManager
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 解析 dataSource
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // 設(shè)置 environment 到 configuration
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

private TransactionFactory transactionManagerElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      // 通過反射實例化
      TransactionFactory factory = (TransactionFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a TransactionFactory.");
  }

  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      // 通過反射實例化
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

其中主要是兩部分,第一部分解析 transactionManager,第二部分解析 dataSource。從 transactionManagerElementdataSourceElement 中可以看出通過對應(yīng) Class 文件的 newInstance 實例化出對應(yīng)的工廠對象。最終解析出的 transactionManagerdataSource 依然是設(shè)置到 Configuration 中。

mappers

mappers 對應(yīng)了具體的 SQL Mapper 文件,也是我們要分析的重點。

mappers 標簽可以多種子標簽,上面的示例中是 mapper 配合 resource

 
    
  

我們下面看一下此種形式在源碼中的解析:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // 這個分支解析 resource 形式的標簽
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 創(chuàng)建 XMLMapperBuilder 
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 進行解析
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

resource 標簽解析的對應(yīng)分支是 (resource != null && url == null && mapperClass == null),其中創(chuàng)建了一個 XMLMapperBuilder 對象然后調(diào)用 parse 方法進行解析:

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 解析 mapper 下面的標簽,包括 namespace、cache、parameterMap、resultMap 等
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

 private void configurationElement(XNode context) {
    try {
      // namespace 對應(yīng) Mapper 對應(yīng)接口的全名(包名 + 類名)
      String namespace = context.getStringAttribute("namespace");
      builderAssistant.setCurrentNamespace(namespace);
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      // 解析生成 ParameterMap
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      // 解析生成 ResultMap
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      sqlElement(context.evalNodes("/mapper/sql"));
      // 每一個 sql 語句生成一個 MappedStatement
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new RuntimeException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

configurationElement 用于解析具體的子標簽,如 namespace、cache、parameterMap、resultMap 以及 select|insert|update|delete 等。

namespace 對應(yīng)了 Mapper 接口類的包名 + 類名,通過 namespace 可以唯一定位一個 Class 文件,解析的 namespace 保存在 builderAssistant 中,后面會用到。

parameterMapresultMap 解析會生成 ParameterMapResultMap 對象。每個 SQL 語句解析會生成 MappedStatement。

在上面的 parse 方法中,解析完標簽后調(diào)用了 bindMapperForNamespace,這個實現(xiàn)了加載 namespace 對應(yīng)的 Class,并且為每個 Class 創(chuàng)建了代理類工廠對象(MapperProxyFactory)。

MapperProxyFactory

MapperProxyFactory 用于為 Mapper 接口類創(chuàng)建代理對象,代理對象指的是
BlogMapper mapper = session.getMapper(BlogMapper.class) 生成的對象。

下面從 bindMapperForNamespace 開始:

private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class boundType = null;
      try {
        // 加載類
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          // 添加 mapper 和 MapperProxyFactory
          configuration.addMapper(boundType);
        }
      }
    }
  }

其中先從 builderAssistant 取出 namespace,然后加載對應(yīng)的 Class(boundType = Resources.classForName(namespace))。最后調(diào)用 configuration.addMapper(boundType) 添加到 configuration 中。 configuration.addMapper(boundType) 很關(guān)鍵,看代碼:

public  void addMapper(Class type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 添加到  Map, MapperProxyFactory> 中
        knownMappers.put(type, new MapperProxyFactory(type));
        // It"s important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won"t try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

關(guān)鍵的一行是 knownMappers.put(type, new MapperProxyFactory(type)),其中 knownMappers 的類型是 Map, MapperProxyFactory>,即 key 是 Class,value 是 MapperProxyFactory。這里的 MapperProxyFactory 即是動態(tài)代理對象的工廠,下面是其 newInstance 方法的代碼:

  protected T newInstance(MapperProxy mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

從中可以看出,這里用的是 Java 的動態(tài)代理,Proxy.newProxyInstance 方法生成指定接口的代理對象,這個方法的第三個參數(shù)是用于方法攔截的對象,這里是 MapperProxy 的實例。

由此可以知道,具體的執(zhí)行 SQL 語句的操作是由這個類攔截并且執(zhí)行的,看看這個類的 invoke 方法:

  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    if (Object.class.equals(method.getDeclaringClass())) {
      return method.invoke(this, args);
    }
    final MapperMethod mapperMethod = cachedMapperMethod(method);
    return mapperMethod.execute(sqlSession, args);
  }

如果是 Object 類中聲明的方法,則直接執(zhí)行,否則調(diào)用 MapperMethodexecute,其中便是 JDBC 相關(guān)的邏輯了。限于篇幅,具體內(nèi)容留到下一篇文章再看。

在分析完配置文件的解析后,再回到 XMLConfigBuilder 中:

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 構(gòu)建 SqlSessionFactory
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

parser.parse() 執(zhí)行完后,生成一個 Configuration 對象,最后調(diào)用 build 構(gòu)建 SqlSessionFactory,代碼如下:

  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

可以看到,最終創(chuàng)建的是 DefaultSqlSessionFactory,這個類內(nèi)部持有 Configuration,并且提供了多個重載的 openSession 方法用于創(chuàng)建 SqlSession

到這里,初始化部分就結(jié)束了。

總結(jié)

MyBatis 的初始化流程主要是解析配置文件,將相關(guān)信息保存在 Configuration 中,同時對每個 namespace 代表的 Class 生成代理對象工廠。最后,利用 Configuration 生成了一個 DefaultSqlSessionFactory,通過這個對象可以創(chuàng)建 SqlSession 執(zhí)行 SQL 請求,相關(guān)內(nèi)容將在下一篇(MyBatis 源碼解析(二):SqlSession 執(zhí)行流程)分析。

如果我的文章對您有幫助,不妨點個贊支持一下(^_^)

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/72677.html

相關(guān)文章

  • Mybatis源碼分析

    摘要:我認為學習框架源碼分為兩步抓住主線,掌握框架的原理和流程理解了處理思路之后,再去理解面向?qū)ο笏枷牒驮O(shè)計模式的用法目前第一步尚有問題,需要多走幾遍源碼,加深下理解,一起加油 這篇文章我們來深入閱讀下Mybatis的源碼,希望以后可以對底層框架不那么畏懼,學習框架設(shè)計中好的思想; 架構(gòu)原理 架構(gòu)圖 showImg(https://segmentfault.com/img/remote/...

    lindroid 評論0 收藏0
  • MyBatis 源碼解析(二):SqlSession 執(zhí)行流程

    摘要:簡介上一篇文章源碼解析一初始化和動態(tài)代理分析了解析配置文件以及動態(tài)代理相關(guān)的源碼,這一篇接著上一篇探究的執(zhí)行流程,另外了解一下中的緩存??偨Y(jié)本文主要分析了的執(zhí)行流程,結(jié)合上一篇文章基本了解了的運行原理。 簡介 上一篇文章(MyBatis 源碼解析(一):初始化和動態(tài)代理)分析了 MyBatis 解析配置文件以及 Mapper 動態(tài)代理相關(guān)的源碼,這一篇接著上一篇探究 SqlSessio...

    Dionysus_go 評論0 收藏0
  • 【深入淺出MyBatis筆記】MyBatis解析運行原理

    摘要:的解析和運行原理構(gòu)建過程提供創(chuàng)建的核心接口。在構(gòu)造器初始化時會根據(jù)和的方法解析為命令。數(shù)據(jù)庫會話器定義了一個對象的適配器,它是一個接口對象,構(gòu)造器根據(jù)配置來適配對應(yīng)的對象。它的作用是給實現(xiàn)類對象的使用提供一個統(tǒng)一簡易的使用適配器。 MyBatis的解析和運行原理 構(gòu)建SqlSessionFactory過程 SqlSessionFactory提供創(chuàng)建MyBatis的核心接口SqlSess...

    bitkylin 評論0 收藏0
  • 開源框架解析,手寫MyBatis細節(jié)思路

    摘要:基本綱要組成動態(tài)配置配置核心源碼分析源碼解析源碼解析源碼解析源碼解析手寫框架是什么本質(zhì)是一種半自動的框架,前身是其源于和的組合,除了和映射關(guān)系之外,還需要編寫語句映射三要素映射規(guī)則快速入門加入的依賴添加的配置文件場景介紹編寫實體類接口以及文 showImg(https://segmentfault.com/img/bVblrnC); Mybatis基本綱要 Mybatis組成 · 動態(tài)...

    paulli3 評論0 收藏0
  • 源碼的角度解析Mybatis的會話機制

    摘要:從源碼的角度分析源碼分析從哪一步作為入口呢如果是看過我之前寫的那幾篇關(guān)于的源碼分析,我相信你不會在源碼前磨磨蹭蹭,遲遲找不到入口。 微信公眾號「后端進階」,專注后端技術(shù)分享:Java、Golang、WEB框架、分布式中間件、服務(wù)治理等等。 老司機傾囊相授,帶你一路進階,來不及解釋了快上車! 坐在我旁邊的鐘同學聽說我精通Mybatis源碼(我就想不通,是誰透漏了風聲),就順帶問了我一個...

    DevWiki 評論0 收藏0

發(fā)表評論

0條評論

最新活動
閱讀需要支付1元查看
<