Mybatis源碼學習(一)-總體框架理解

1、對源碼先上一個結構圖:java

源代碼主要在org.apache.ibatis目錄下,18個包,其中在應用中主要的包有:builder、session、cache、type、transaction、datasource、jdbc、mapping,提供支撐服務的包有annotation、binding、io、logging、plugin、reflection、scripting、exception、executor、parsingapache

2、從使用入手緩存

MyBatis使用的三板斧是SqlSessionFactoryBuilder和SqlSessionFactory、SqlSessionsession

  1. SqlSessionFactoryBuilderapp

    支持9種構造方法,其實最主要的是包含Configuration對象的構造方法,目的是爲了經過加載配置文件創造SqlSessionFactory對象,真實最終返回的是DefaultSqlSessionFactory對象框架

    全部的構造方法最終都是調用build(Configuratiron)方法,這就要來研究一下Configuration對象,其實他就是對xml配置文件的對象映射,關於xml文件結構組成可從源碼中看出以下:ide

    本文介紹一下Configuration的大框架,後續開個專輯專門研究Configuration的細節fetch

      propertiesElement(root.evalNode("properties")); //加載資源文件屬性和當前文件屬性 
 
      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"));

        環境元素(數據源和事務)、 屬性、類型別名、typeHandler、mapper、setting、插件ui

  • 屬性:url

     先來看屬性的加載,屬性的加載最重要的是瞭解三種屬性來源(屬性配置文件、當前文件的屬性、java代碼輸入)和三種屬性的加載順序(先加載配置文件,在加載config文件的屬性,最後加載java代碼輸入)

private void propertiesElement(XNode context) throws Exception {

    if (context != null) {

      Properties defaults = context.getChildrenAsProperties();

      String resource = context.getStringAttribute("resource");

      String url = context.getStringAttribute("url");

      if (resource != null && url != null) {

        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");

      }

      if (resource != null) {

        defaults.putAll(Resources.getResourceAsProperties(resource));

      } else if (url != null) {

        defaults.putAll(Resources.getUrlAsProperties(url));

      }

      Properties vars = configuration.getVariables();

      if (vars != null) {

        defaults.putAll(vars);

      }

      parser.setVariables(defaults);

      configuration.setVariables(defaults);

    }

  }

  • 類型別名

    主要是類的完整路徑和簡單別名的對應關係加載,保存在容器typeAliasRegistry中,最終映射到configuratiron對象中

private void typeAliasesElement(XNode parent) {

    if (parent != null) {

      for (XNode child : parent.getChildren()) {

        if ("package".equals(child.getName())) {

          String typeAliasPackage = child.getStringAttribute("name");

          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);

        } else {

          String alias = child.getStringAttribute("alias");

          String type = child.getStringAttribute("type");

          try {

            Class<?> clazz = Resources.classForName(type);

            if (alias == null) {

              typeAliasRegistry.registerAlias(clazz);

            } else {

              typeAliasRegistry.registerAlias(alias, clazz);

            }

          } catch (ClassNotFoundException e) {

            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);

          }

        }

      }

    }

  }

  • typeHandler

    用戶自定義類型處理器,保存在typeHandlerRegistry容器中,最終保存在configuration對象中

private void typeHandlerElement(XNode parent) throws Exception {

    if (parent != null) {

      for (XNode child : parent.getChildren()) {

        if ("package".equals(child.getName())) {

          String typeHandlerPackage = child.getStringAttribute("name");

          typeHandlerRegistry.register(typeHandlerPackage);

        } else {

          String javaTypeName = child.getStringAttribute("javaType");

          String jdbcTypeName = child.getStringAttribute("jdbcType");

          String handlerTypeName = child.getStringAttribute("handler");

          Class<?> javaTypeClass = resolveClass(javaTypeName);

          JdbcType jdbcType = resolveJdbcType(jdbcTypeName);

          Class<?> typeHandlerClass = resolveClass(handlerTypeName);

          if (javaTypeClass != null) {

            if (jdbcType == null) {

              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);

            } else {

              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);

            }

          } else {

            typeHandlerRegistry.register(typeHandlerClass);

          }

        }

      }

    }

  }

  • mapper

    主要是加載mapper.xml文件,同構mapperParser對文件進行解析,保存在容器mapperRegistry中,最終保存到configuration對象中

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");

          if (resource != null && url == null && mapperClass == null) {

            ErrorContext.instance().resource(resource);

            InputStream inputStream = Resources.getResourceAsStream(resource);

            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.");

          }

        }

      }

    }

  }

  • 環境元素

    主要加載數據源、和事務配置信息,由Environment.Builder對象進行處理,並將信息保存到configuratiron對象

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)) {

          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));

          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));

          DataSource dataSource = dsFactory.getDataSource();

          Environment.Builder environmentBuilder = new Environment.Builder(id)

              .transactionFactory(txFactory)

              .dataSource(dataSource);

          configuration.setEnvironment(environmentBuilder.build());

        }

      }

    }

  }

  •  setting

加載ibatis自身工做須要配置的全部設置信息,爲configuration對象進行賦值

 private void settingsElement(XNode context) throws Exception {

    if (context != null) {

      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")));

      configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));

      configuration.setInjectionFilterEnabled(booleanValueOf(props.getProperty("injectionFilterEnabled"), false));

      configuration.setInjectionFilter(parseExpression(props.getProperty("injectionFilter"), "^[a-zA-Z0-9._]*$"));

    }

  }

 二、SqlSessionFactory,真實幹活的DefaultSqlSessionFactory

SqlSessionFactory的做用是承上啓下,做爲SqlSession的工廠,主要是工做必須是提供獲取SqlSession的方法,同時還提供了一個獲取Configuration的方法

其中SqlSessionFactory提供了8種獲取SqlSession的方法,主要涉及4個參數:是否自動提交、事務級別、ExecutorType(Statement類型【普通、預處理、批處理】)、自定義Connection

其中須要注意的是openSession()方法默認不是自動commit的。

  SqlSession openSession();
  SqlSession openSession(boolean autoCommit);
  SqlSession openSession(Connection connection);
  SqlSession openSession(TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType);
  SqlSession openSession(ExecutorType execType, boolean autoCommit);
  SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
  SqlSession openSession(ExecutorType execType, Connection connection);
  Configuration getConfiguration();

揭開如何提供SqlSession的祕密(在這裏發現了Configuration還能夠提供創造Executor的工做,Connection被封裝在Transaction 中,Configuration作了兩件事情,一個是配置信息存儲,還有內部對象的組裝工做,須要深刻研究這種模式是否能夠經過另外一個Action對象進行管理呢?)

private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
  private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
    try {
      boolean autoCommit;
      try {
        autoCommit = connection.getAutoCommit();
      } catch (SQLException e) {
        // Failover to true, as most poor drivers
        // or databases won't support transactions
        autoCommit = true;
      }      
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      final Transaction tx = transactionFactory.newTransaction(connection);
      final Executor executor = configuration.newExecutor(tx, execType);
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
這個須要重點關注。建立事務,將Connection傳遞給tx的過程。

 三、SqlSession,真實幹活的是DefaultSqlSession

SqlSession類主要封裝了Configuration對象、Executor對象、是否自動提交

SqlSession是在程序中真實幹活的人,咱們在使用Mybatis中打交道最頻繁的就是SqlSessinon對象,上個結構圖,看看他都在幹啥

 

 看到圖了,就沒有什麼祕密了,他就是在幹數據的平常操做的活,只不過是用他本身封裝的一套東西,好比說Configuration(封裝Connection【來源可使Pool】)、Executor(封裝Statement)、ResultHandler(封裝處理ResultSet對象)、RowBounds(封裝分頁對象),提供了CRUD,提供了緩存機制,提供了根據配置文件獲取Sql語句的方法,提供了事務的提交和回滾等。

相關文章
相關標籤/搜索