[ハンドティア MyBatis ソースコード] MyBatis 起動プロセス、構成設定システム

MyBatisの起動プロセス

一般的なプロセスは次のとおりです。

  • 設定XMLファイルをロードする
  • mybatisのdtd記述ファイルを読み込み、xmlタグを解析する
  • XML 構成情報を読み取って、対応するグローバル構成オブジェクトを生成し、マッパーを必要とする SQL マッピングを生成します。
  • SqlSessionFactory を作成したら、SqlSessionFactory を使用してセッションを作成します。

ここに画像の説明を挿入

  • 構成: Mybatis 初期化プロセスの中心となるオブジェクトであり、Mybatis のほとんどすべての構成情報が構成に保存され、グローバルに有効になります。
  • XMLConfigBuilder: 構成を作成し、MyBatis 構成ファイル内の構成ノードの構成情報を解析して、それを構成オブジェクトに移入するために使用されます。
  • XMLMapperEntityResolver: XMLConfigBuilder に含まれており、ローカル DTD ファイルを読み取るために使用されます。
  • XPathParser: XPath パーサー。JDK クラスをカプセル化し、XMLConfigBuilder に含まれています。XPath は XML パス言語 (XML Path Language) であり、XML ドキュメントの特定の部分の位置を決定するために使用される言語です。
  • ドキュメント : XPathParser に含まれており、後で XPathParser と組み合わせて設定ファイルの内容を読み取るのに便利です。

SQLSessionFactoryの作成

InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

public SqlSessionFactory build(InputStream inputStream) {
    
    
    return build(inputStream, null, null);
}

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    
    
    // 准备阶段:将配置文件加载到内存中并生成document对象,然后创建初始化Configuration对象
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
  // 解析document对象并生成 SqlSessionFactory
  //parser.parse()返回的就是一个Configuration对象,对应的就是下面的方法
    return build(parser.parse());
}

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

作成した SQLSessionFactory は実際には DefaultSqlSessionFactory であり、その作成には Configuration オブジェクトが必要です

XMLConfigBuilder の作成

SqlSessionFactory を作成するには以下が必要です。

  • XMLConfigBuilder を利用して構成ファイルを読み取り、解析し、構成ファイルを Document オブジェクトに変換します。
  • Configuration オブジェクトを初期化し、構成ファイルからプロパティを設定します。
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    
    
   this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    
    
   // 调用父类初始化configuration
   super(new Configuration());
   ErrorContext.instance().resource("SQL Mapper Configuration");    
   // 将Properties全部设置到configuration里面去
   this.configuration.setVariables(props);
   this.parsed = false;
   this.environment = environment;
  // 绑定 XPathParser
   this.parser = parser;
}

Configuration を初期化すると、使用するクラスとそのエイリアスが typeAliases マップに登録されます。

private final Map<String, Class<?>> typeAliases = new HashMap<>();
public Configuration() {
    
    
  typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
  typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

  typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
  typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
  typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

  typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
  typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
  typeAliasRegistry.registerAlias("LRU", LruCache.class);
  typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
  typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

  typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

  typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
  typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

  typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
  typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
  typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
  typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
  typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
  typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
  typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

  typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
  typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

  languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
  languageRegistry.register(RawLanguageDriver.class);
}

XPathParser の作成

XPathParser には、ローカル DTD ファイルを読み取るための XMLMapperEntityResolver オブジェクト、XPath オブジェクト、XML ファイルを保存するための Document オブジェクト、プロパティ タグで定義されたキーと値のペアのコレクション オブジェクト変数など、多くの重要なオブジェクトが含まれています。

ここに画像の説明を挿入

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    
    
  this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    
    
  // 初始化属性
  commonConstructor(validation, variables, entityResolver);
  // 读取xml文件,保存为 document
  this.document = createDocument(new InputSource(inputStream));
}
public class XPathParser {
    
    
  private final Document document;
  private EntityResolver entityResolver;
  private Properties variables;
  private XPath xpath;
}

構成内のプロパティを解析して設定する

MyBatis の構成分析は<configuration>ラベルに基づいて行われます。

構成ファイルを見てみましょう。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!--SqlSessionFactoryBuilder中配置的配置文件的优先级最高;config.properties配置文件的优先级次之;properties标签中的配置优先级最低 -->
    <properties resource="org/mybatis/example/config.properties">
      <property name="username" value="dev_user"/>
      <property name="password" value="F2Fa3!33TYyg"/>
    </properties>

    <!--一些重要的全局配置-->
    <settings>
    <setting name="cacheEnabled" value="true"/>
    <!--<setting name="lazyLoadingEnabled" value="true"/>-->
    <!--<setting name="multipleResultSetsEnabled" value="true"/>-->
    <!--<setting name="useColumnLabel" value="true"/>-->
    <!--<setting name="useGeneratedKeys" value="false"/>-->
    <!--<setting name="autoMappingBehavior" value="PARTIAL"/>-->
    <!--<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>-->
    <!--<setting name="defaultExecutorType" value="SIMPLE"/>-->
    <!--<setting name="defaultStatementTimeout" value="25"/>-->
    <!--<setting name="defaultFetchSize" value="100"/>-->
    <!--<setting name="safeRowBoundsEnabled" value="false"/>-->
    <!--<setting name="mapUnderscoreToCamelCase" value="false"/>-->
    <!--<setting name="localCacheScope" value="STATEMENT"/>-->
    <!--<setting name="jdbcTypeForNull" value="OTHER"/>-->
    <!--<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>-->
    <!--<setting name="logImpl" value="STDOUT_LOGGING" />-->
    </settings>

    <typeAliases>

    </typeAliases>

    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!--默认值为 false,当该参数设置为 true 时,如果 pageSize=0 或者 RowBounds.limit = 0 就会查询出全部的结果-->
            <!--如果某些查询数据量非常大,不应该允许查出所有数据-->
            <property name="pageSizeZero" value="true"/>
        </plugin>
    </plugins>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://10.59.97.10:3308/windty"/>
                <property name="username" value="windty_opr"/>
                <property name="password" value="windty!234"/>
            </dataSource>
        </environment>
    </environments>

    <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql" />
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>

    <mappers>
        <!--这边可以使用package和resource两种方式加载mapper-->
        <!--<package name="包名"/>-->
        <!--<mapper resource="./mappers/SysUserMapper.xml"/>-->
        <mapper resource="./mappers/CbondissuerMapper.xml"/>
    </mappers>

</configuration>

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());

// XMLConfigBuilder
public Configuration parse() {
    
    
  // 根据parsed变量的值判断是否已经完成了对mybatis-config.xml配置文件的解析
  if (parsed) {
    
    
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  // 在mybatis-config.xml配置文件中查找<configuration>节点,并开始解析
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

この parseConfiguration メソッドに進みます。

private void parseConfiguration(XNode root) {
    
    
    try {
    
    
      //issue #117 read properties first
      //解析properties标签,并set到Configration对象中
      //在properties配置属性后,在Mybatis的配置文件中就可以使用${key}的形式使用了。
      propertiesElement(root.evalNode("properties"));
      
      //解析setting标签的配置
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      //添加vfs的自定义实现,这个功能不怎么用
      loadCustomVfs(settings);
        
      //配置类的别名,配置后就可以用别名来替代全限定名
      //mybatis默认设置了很多别名,参考附录部分
      typeAliasesElement(root.evalNode("typeAliases"));
        
      //解析拦截器和拦截器的属性,set到Configration的interceptorChain中
      //MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
      //Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
        //ParameterHandler (getParameterObject, setParameters)
        //ResultSetHandler (handleResultSets, handleOutputParameters)
        //StatementHandler (prepare, parameterize, batch, update, query)
      pluginElement(root.evalNode("plugins"));
      
      //Mybatis创建对象是会使用objectFactory来创建对象,一般情况下不会自己配置这个objectFactory,使用系统默认的objectFactory就好了
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
       
      //设置在setting标签中配置的配置
      settingsElement(settings);
   
      //解析环境信息,包括事物管理器和数据源,SqlSessionFactoryBuilder在解析时需要指定环境id,如果不指定的话,会选择默认的环境;
      //最后将这些信息set到Configration的Environment属性里面
      environmentsElement(root.evalNode("environments"));
        
      //
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
        
      //无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。解析typeHandler。
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析Mapper
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
    
    
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
}

構成ファイルの解析には、xpath とドキュメント オブジェクトの式が必要です。ラベル名に従ってノードを照合し、対応する値を取り出し、ノードを見つけて XNode オブジェクトを返し、対応する値を構成に設定します。

//XPathParser
public XNode evalNode(String expression) {
    
    
  return evalNode(document, expression);
}

いくつかの重要なノードの分析を詳しく見てみましょう。

環境ノードを解析する

たとえば、構成ファイルでは次のようになります。

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>

private void environmentsElement(XNode context) throws Exception {
    
    
  if (context != null) {
    
    
    // 未指定XMLConfigBuilder.environment字段,则使用default属性
    if (environment == null) {
    
    
      environment = context.getStringAttribute("default");
    }
    // 遍历子节点
    for (XNode child : context.getChildren()) {
    
    
      String id = child.getStringAttribute("id");
      // 与XmlConfigBuilder.environment字段匹配
      if (isSpecifiedEnvironment(id)) {
    
    
        // 创建TransactionFactory
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        // 创建DataSourceFactory和DataSource
        DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
        DataSource dataSource = dsFactory.getDataSource();
        // 创建Environment
        Environment.Builder environmentBuilder = new Environment.Builder(id)
            .transactionFactory(txFactory)
            .dataSource(dataSource);
        // 将Environment对象记录到Configuration.environment字段中
        configuration.setEnvironment(environmentBuilder.build());
        break;
      }
    }
  }
}

マッパータグの解析

<mappers>
    <package name="com.daley.dao"/>
</mappers>

ノード名がパッケージと等しい場合

  • パッケージパスの下にあるすべてのマッパーを自動的にスキャンします。
  • 構成内の MapperRegistry は、対応するパッケージ パスを追加します。
  • 最終マッパーに対応するMapperProxyFactoryをknowMappersに追加します。
private void mapperElement(XNode parent) throws Exception {
    
    
  if (parent != null) {
    
    
    // 处理mapper子节点
    for (XNode child : parent.getChildren()) {
    
    
      // package子节点
      if ("package".equals(child.getName())) {
    
    
        // 自动扫描包下所有映射器
        String mapperPackage = child.getStringAttribute("name");
        // 扫描指定的包,并向mapperRegistry注册mapper接口
        configuration.addMappers(mapperPackage);
      } else {
    
    
        
        // 直接指定Mapper属性
        // 获取mapper节点的resource、url、class属性,三个属性互斥
        String resource = child.getStringAttribute("resource");
        String url = child.getStringAttribute("url");
        String mapperClass = child.getStringAttribute("class");
        // 如果mapper节点指定了resource或者url属性,则创建XmlMapperBuilder对象,并通过该对象解析resource或者url属性指定的mapper配置文件
        if (resource != null && url == null && mapperClass == null) {
    
    
          // 使用类路径
          ErrorContext.instance().resource(resource);
          try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
    
    
            // 创建XMLMapperBuilder对象,解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url != null && mapperClass == null) {
    
    
          // 使用绝对url路径
          ErrorContext.instance().resource(url);
          try(InputStream inputStream = Resources.getUrlAsStream(url)){
    
    
            // 创建XMLMapperBuilder对象,解析映射配置文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          }
        } else if (resource == null && url == null && mapperClass != null) {
    
    
          // 如果mapper节点指定了class属性,则向MapperRegistry注册该mapper接口
          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.");
        }
      }
    }
  }
}

ここに画像の説明を挿入

//MapperRegistry
public void addMappers(String packageName) {
    
    
    addMappers(packageName, Object.class);
  }
public void addMappers(String packageName, Class<?> superType) {
    
    
  // 查找包下所有父类是 Objects.class 的类
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  for (Class<?> mapperClass : mapperSet) {
    
    
    addMapper(mapperClass);
  }
}
// MapperRegistry.addMapper()
public <T> void addMapper(Class<T> type) {
    
    
 ……
    boolean loadCompleted = false;
    // 将Mapper接口对应的Class对象和MapperProxyFactory对象添加到knownMappers集合
    knownMappers.put(type, new MapperProxyFactory<>(type));
    
    // 对被注解接口的解析逻辑
    MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
    parser.parse();
    loadCompleted = true;
}

マッパーのマッピング関係を追加すると、マッパー インターフェイスで関連する注釈の分析が開始されます。

  • マッパー クラスに対応するマッパー XML ファイルが読み取られ、ファイル内のコンテンツは読み取り後に解析されます。
  • 変数マッパー クラスのメソッドごとに、resultMap などの分析用にマークされたアノテーションを読み取ります。
public void parse() {
    
    
  String resource = type.toString();
  // 检测是否已经加载过该接口
  if (!configuration.isResourceLoaded(resource)) {
    
    
    // 检测是否加载过对应的映射配置文件,如果未加载,则创建XMLMapperBuilder对象解析对应的mapper映射xml文件
    loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    // 解析@CacheNamespace注解
    parseCache();
    // 解析@CacheNamespaceRef注解
    parseCacheRef();
    //开始解析每个方法
    for (Method method : type.getMethods()) {
    
    
      if (!canHaveStatement(method)) {
    
    
        continue;
      }
      if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
          && method.getAnnotation(ResultMap.class) == null) {
    
    
        parseResultMap(method);
      }
      try {
    
    
        // 解析@SelectKey,@ResultMap等注解,并创建MappedStatement对象
        parseStatement(method);
      } catch (IncompleteElementException e) {
    
    
        // 如果解析过程出现IncompleteElementException异常,可能是引用了未解析的注解,此处将出现异常的方法添加到incompleteMethod集合中保存
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  // 遍历incompleteMethods集合中记录的未解析的方法,并重新进行解析
  parsePendingMethods();
}

loadXmlResource メソッドは、parse メソッドを呼び出して、オブジェクト xml のマッパー内のコンテンツを解析します。

public void parse() {
    
    
  // 判断是否已经加载过该映射文件
  if (!configuration.isResourceLoaded(resource)) {
    
    
    // 处理mapper节点
    configurationElement(parser.evalNode("/mapper"));
    // 将resource添加到Configuration.loadedResources集合中保存,他是hashset类型的集合,其中记录了已经加载过的映射文件
    configuration.addLoadedResource(resource);
    // 绑定映射器到namespace
    bindMapperForNamespace();
  }

  // 处理ConfigurationElement方法中解析失败的resultMap节点
  parsePendingResultMaps();
  // 处理ConfigurationElement方法中解析失败的cache-ref节点
  parsePendingCacheRefs();
  // 处理ConfigurationElement方法中解析失败的SQL语句节点
  parsePendingStatements();
}

configurationElement メソッド: SQL、resultMap、parameterMap などのノードを解析します。

private void configurationElement(XNode context) {
    
    
  try {
    
    
    // 获取mapper节点的namespace属性
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.isEmpty()) {
    
    
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    // 设置MapperBuilderAssistant的currentNamespace字段,记录当前命名空间
    builderAssistant.setCurrentNamespace(namespace);
    // 解析cache-ref节点
    cacheRefElement(context.evalNode("cache-ref"));
    // 解析cache节点
    cacheElement(context.evalNode("cache"));
    // 解析parameterMap节点
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    // 解析resultMap节点
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    // 解析sql节点
    sqlElement(context.evalNodes("/mapper/sql"));
    // 解析select、update、insert、delete等SQL节点
    // 就在这里开始创建sql与方法的映射关系并且保存
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    
    
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

ここまでで構成が作成され、作成された SqlSessionFactory が返されます。

ここに画像の説明を挿入

openSessionのプロセス

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);
      //根据获取的执行器创建SqlSession
      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();
    }
  }
//interceptorChain生成代理类,具体参见Plugin这个类的方法
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    
    
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
    
    
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
    
    
      executor = new ReuseExecutor(this, transaction);
    } else {
    
    
      executor = new SimpleExecutor(this, transaction);
    }
    if (cacheEnabled) {
    
    
      executor = new CachingExecutor(executor);
    }
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

Executor は、CacheExecutor と通常の Executor の 2 つのカテゴリに分類されます。
通常のエグゼキュータは、SimpleExecutor、ReuseExecutor、および BatchExecutor の 3 つの基本エグゼキュータに分かれています。

  • SimpleExecutor: update または select が実行されるたびに、Statement オブジェクトが開き、Statement オブジェクトが使い果たされるとすぐに閉じられます。
  • ReuseExecutor: 更新または選択を実行し、sql をキーとして Statement オブジェクトを検索し、存在する場合は使用し、存在しない場合は作成します。使用後、Statement オブジェクトは閉じられず、次回の使用のために Map<String, Statement> に配置されます。つまり、Statement オブジェクトが再利用されます。
  • BatchExecutor: 更新を実行し (選択なし、JDBC バッチ処理は選択をサポートしません)、すべての SQL をバッチ処理に追加し (addBatch())、統合実行を待機します (executeBatch())。複数の Statement オブジェクトをキャッシュし、各 Statement オブジェクトは addBatch() が完了した後、executeBatch() バッチ処理を 1 つずつ実行するのを待ちます。JDBC バッチ処理と同じです。
    アクションの範囲: Executor のこれらの機能は、SqlSession ライフ サイクルの範囲内に厳密に制限されています。

CacheExecutor は実際には通常の Executor をカプセル化します。通常の Executor との違いは、クエリの前に最初にキャッシュに結果があるかどうかを確認することです。存在する場合は、キャッシュ内の結果を使用します。存在しない場合でも、通常の Executor を使用してクエリを実行し、クエリ結果をキャッシュに保存します。

ここに画像の説明を挿入

構成の概要

Configuration は、MyBatis 設定システム全体の集中管理センターであり、Executor、StatementHandler、Cache、MappedStatement など、上記のコンポーネントのほとんどは、これによって直接または間接的に作成および管理されます。さらに、これらのコンポーネントの動作に影響を与えるプロパティ設定も保存され、維持されます。例:cacheEnabled、lazyLoadingEnabled...など。まさにMyBatisの大家政婦のイメージですね。

Configuration は MyBatis の非常に重要なクラスであり、MyBatis のグローバル構成情報を表します。

構成には、主に次のような多くの重要な情報が含まれています。

  1. 環境: 複数の環境情報を保存します。各環境にはtransactionFactoryとdataSourceがあります。
  2. マッパー: すべてのマッパー ファイルの位置情報を保存します。
  3. mappedStatement: すべてのmappedStatement情報を格納します。各mappedStatementはSQL文を表します。
  4. resultMap: すべての resultMap 情報を格納し、resultMap は結果セットのマッピングを表します。
  5. パラメータマップ: 古いバージョンで使用され、非推奨になりました。
  6. keyGenerators: すべての keyGenerator 情報を保存し、keyGenerator は主キーの生成に使用されます。
  7. typeHandlers: すべての typeHandler 情報を保存します。typeHandler は javaType と jdbcType の間の変換に使用されます。
  8. objectFactory: オブジェクト ファクトリ。ターゲット オブジェクトをインスタンス化するために使用されます。
  9. objectWrapperFactory: オブジェクト ラッパー ファクトリ。ターゲット オブジェクトのプロキシ オブジェクトを作成するために使用されます。
  10. インターセプター: すべてのインターセプター情報を保存します。インターセプターは SQL を実行するインターセプターに使用されます。
  11. databaseIdProvider:databaseId に従って、対応する mappedStatement を取得します。
  12. logImpl: ログファクトリー;
  13. キャッシュ: キャッシュ名前空間;

Configuration によって保存されたすべての情報は、MyBatis セッション (SqlSession) の構成環境全体を構成します。新しい SqlSession が開かれるたびに、SqlSession に必要なすべての情報が Configuration に従って構築されます

つまり、Configuration には MyBatis のグローバル構成情報が保存され、MyBatis 構成環境を表します。これには、SQL マッピング ステートメント、結果セット マッピング、キャッシュ メカニズム、および MyBatis セッションに必要なすべての構成コンテンツを構成するその他の情報が含まれます。

  • build SqlSessionFactoryを呼び出すとSqlSessionFactoryBuilder.build(reader)最下層は、構成ファイルに従って構成オブジェクトを構築します。

  • openSession()セッションを開くために呼び出すと、SqlSession は Configuration と Executor で構成され、この Configuration は以前に構築されたグローバル構成情報です。

したがって、構成は MyBatis にとって非常に重要であることがわかります。構成には、MyBatis セッションに必要なすべての構成リソースと環境情報が含まれています。構成がなければ、SqlSession はまったく構築できず、機能することもできません。

構成の中核となる役割と構成ソース

構成 構成ソースは 3 つあります。

  • Mybatis-config.xml スタートアップ ファイル、グローバル構成、およびグローバル コンポーネントはすべてここから派生します。
  • Mapper.xml SQL マッピング (MappedStatement)\結果セット マッピング (ResultMapper) はすべてここから来ています。
  • @Annotation SQL マッピングと結果セット マッピングの別の形式の式。

ここに画像の説明を挿入

次に、構成の中核的な役割について説明します。

  • 設定 (設定) から得られるグローバル構成情報を保存します。
  • グローバルベースコンポーネントの初期化と維持
    • typeAliases (型の別名)
    • typeHandlers (型ハンドラー)
    • プラグイン
    • 環境 (環境構成)
    • キャッシュ (二次キャッシュ領域)
  • MappedStatement の初期化と維持
  • プラグインで強化されたコンポーネントビルダー
    • newExecutor (エグゼキュータ)
    • newStatementHandler (JDBC ハンドラー)
    • newResultSetHandler (結果セットハンドラー)
    • newParameterHandler (パラメータハンドラ)

ここで構成を使用して Executor を作成するのはなぜですか? その理由は次のとおりです。

1. 構成の種類に応じて作成します

2. キャッシュを有効にするかどうか

3. interceptorChain を使用してプラグインを導入する

これにより、統一されたパッケージングとコンポーネントの標準化が実現されます。
ここに画像の説明を挿入

他のいくつかのコンポーネントの作成にも同じことが当てはまります。

ここに画像の説明を挿入

いくつかのコンポーネントについて説明しましょう。

マッパーレジストリ

マッパー インターフェイスを登録し、その動的プロキシ オブジェクトを生成するために使用されます。
キャッシュ

キャッシュ、アプリケーションレベルのクロスセッション、すべてのキャッシュはロード後に設定に配置されます。次に、mappedStatement がキャッシュとの関連付け操作を実行し、それらの間には 1:1 の関係があります。

TypeAliasRegistry

TypeAliasRegistry は、MyBatis に型エイリアスを格納するためのレジストリです。
MyBatis では、<typeAlias>タグを使用して、次のような Java 型のエイリアスを設定できます。

<typeAlias type="com.someapp.model.User" alias="User"/>

これにより、User が com.someapp.model.User タイプのエイリアスになり、後続のマッピング ファイルでそのタイプを表すために User を直接使用できます。

では、MyBatis はこの型エイリアス関数をどのように実装するのでしょうか?

これは TypeAliasRegistry に依存します

  • すべてのタイプ エイリアス構成は TypeAliasRegistry に保存されます。SQL マッピング ファイルが解析されると、MyBatis は構成を検索し<typeAlias>、見つかったタイプ エイリアスをレジストリに登録します。
  • 次に、後続の解析プロセスで、MyBatis が型を検出すると、まず TypeAliasRegistry にその型の別名があるかどうかを確認し、存在する場合はその別名を使用し、そうでない場合は完全修飾名を使用します。

したがって、TypeAliasRegistry の役割は、タイプ エイリアス設定を保存し、必要に応じて MyBatis がこれらのエイリアスを検索して使用できるようにすることですMyBatisの型エイリアス機能を実装します。

TypeAliasRegistry は、MyBatis のコアコンポーネントの 1 つとして、主に次の機能を備えています。

  1. タイプ エイリアス設定の保存: MyBatis は、マッピング ファイルから解析されたタイプ エイリアスを、その後の検索と使用のために登録できます。
  2. エイリアスの検索: MyBatis の解析プロセス中、型が必要になると、MyBatis はまず TypeAliasRegistry で型のエイリアスを検索します。存在する場合はエイリアスを使用し、それ以外の場合は完全修飾名を使用します。
  3. 構成の簡素化: 型エイリアスにより、MyBatis のマッピング構成を簡素化できます。型を使用するたびにその完全修飾名を入力する必要はありません。MyBatis によって設定されたエイリアスを使用するだけで済みます。これにより、設定の読みやすさと効率が向上します。
  4. 解析回数を減らす: MyBatis が完全修飾名の代わりにエイリアスを使用するたびに、そのパッケージの解析を 1 回減らすことができ、MyBatis の解析パフォーマンスをわずかに向上させることができます。

構成要素

構成構成情報は XML と注釈から取得されます。各ファイルと注釈は複数の構成要素で構成され、ネストされた関係を示します。全体的な関係は次の図に示されています。ここに画像の説明を挿入

ここに画像の説明を挿入

各構成の使用については、公式 Web サイトにあるドキュメントを参照してください: https://mybatis.org/mybatis-3/zh/configuration.html#properties
ここに画像の説明を挿入

要素ベアラー

XML であっても、アノテーションであっても、これらの構成要素を保持するには、JAVA 構成属性またはオブジェクト コンポーネントに変換する必要があります。対応関係は次のとおりです。

  • 完全な構成 (config.xml) は、構成オブジェクトのプロパティによって保持されます。
  • SQLマッピング<select|insert...>など@SelectはMappedStatementオブジェクトによって運ばれます
  • キャッシュ<cache..>、または@CacheNamespaceCache オブジェクトによってホストされる
  • 結果セット マップは ResultMap オブジェクトによってホストされます
    ここに画像の説明を挿入

設定ファイルの解析

XMLファイルの解析プロセス

構成ファイルの解析については、まず XML 解析プロセスを分析してから、個別に説明する必要があります。XML 構成解析の最下層では、dom4j を使用して最初にノード ツリーに解析し、次にさまざまなノード タイプに応じてさまざまなパーサーを照合します。最後に特定のコンポーネントに解析されます。

パーサーの基本クラスは BaseBuilder で、グローバルな Configuration オブジェクトが含まれています。この目的は、解析されるすべてのコンポーネントをまとめて Configuration に帰属させる必要があることです。各パーサーが何を行うかを見てみましょう。

  • XMLConfigBuilder: config.xml ファイルを解析すると、グローバル構成を解析するための Configuration オブジェクトが直接作成されます。
  • XMLMapperBuilder: Mapper.xml ファイル、コンテンツに含まれるものなどを解析します。
  • MapperBuilderAssistant: Mapper.xml解析アシスタント Mapper.xmlではStatement(SQL文)とキャッシュを共有しており、この解析により共有コンポーネントの分散を実現します。
  • XMLStatementBuilder: SQL マッピングの解析では、<select|update|insert|delete> 要素が MapperStatement に解析されることを意味します。
  • SqlSourceBuilder: SQL データ ソース解析。宣言された SQL を実行可能な SQL に解析します。
  • XMLScriptBuilder: 動的 SQL データ ソースに設定された SqlNode スクリプトのセットを解析します。

ここに画像の説明を挿入

全体的な解析プロセスは XmlConfigBuilder から始まり、すべてのノードが解析されるまで徐々に内部に解析されます。MappedStatement の解析プロセスを通じて、その全体的な解析プロセスを理解できます。

ここに画像の説明を挿入

フローの説明:

  • 【XmlConfigBuilder】 config.xml入力ストリームを受け取り、空のConfigurationオブジェクトを作成します
    ここに画像の説明を挿入

  • [XmlConfigBuilder] グローバル構成を解析する
    ここに画像の説明を挿入

  • [XmlConfigBuilder] MapperElements 分析、リソースまたは URL を介して Mapper.xml ファイルを指定します

    • 【XmlMapperBuilder】解析キャッシュや結果セット設定などのパブリック設定
    • [XmlMapperBuilder] SQL マッピングを解析 <select|insert|upate|delete>
      • [XMLScriptBuilder] 動的スクリプトを含む SQL データ ソースを解析して生成します。
    • [XmlMapperBuilder] ビルド ステートメント
      • [MapperBuilderAssistant] キャッシュを設定し、Configuration に追加します

アノテーション構成の分析

注釈解析の基礎となる実装は、リフレクションを通じて Mapper インターフェイス内の注釈要素を取得することによって実現されます。次の 2 つの方法があります。

  • 1 つはインターフェイス名を直接指定する方法です。
  • 1 つは、パッケージ名を指定して、そのパッケージ内のすべてのインターフェイス クラスを自動的にスキャンすることです。

これらのロジックは、マッパー レジストリ (MapperRegistry) によって実装されます。インターフェイス クラス パラメーターを受け取り、パラメーターに基づいてインターフェイスの動的プロキシ ファクトリを作成し、内部メソッド アノテーションを解析して各 MapperStatement を生成し、最後にそれを Configuration に追加して分析を完了します。

ここに画像の説明を挿入

さらに詳しく知りたい場合は、次の記事を参照してください:
MyBatis コア構成の概要 - 構成
Mybatis メイン構成の詳細な説明 - 構成

おすすめ

転載: blog.csdn.net/zyb18507175502/article/details/131105326