MyBatis源码阅读(三) --- 配置信息的解析以及SqlSessionFactory构建过程

Java
192
0
0
2024-02-21
标签   MyBatis
一、简介

前面一篇文章我们对Mybatis整体的执行流程做了一个详细的总结,可进入专栏查看;

本篇文章我们将分析一下配置信息是如何解析的以及SqlSessionFactory创建过程。

二、配置信息解析过程

下面我们通过Debug方式点查看Mybatis如何获取配置文件:

//1、读取配置文件
String resource = "mybatis-config.xml";
InputStream inputStream;
try {
    //主要是通过ClassLoader.getResourceAsStream()方法获取指定的classpath路径下的Resource
    inputStream = Resources.getResourceAsStream(resource);
    //2、初始化mybatis,创建SqlSessionFactory类实例
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    System.out.println(sqlSessionFactory);
} catch (IOException e) {
    e.printStackTrace();
}

首先读取mybatis-config.xml全局配置文件,转换成文件流。然后第二步是构造SqlSessionFactory。接着查看build方法,我们来到SqlSessionFactoryBuilder的build()方法,利用XMLConfigBuilder方法中的parseConfiguration方法解析相关的配置,inputStream流是mybatis-config.xml配置对应的字节流,XMLConfigBuilder的parse()方法解析mybatis-config.xml节点得到Configuration配置类。

//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream)
//传入我们classpath路径下mybatis-config.xml的文件流
public SqlSessionFactory build(InputStream inputStream) {
  return build(inputStream, null, null);
}
 
//org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.InputStream, java.lang.String, java.util.Properties)
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    //构造一个XMLConfigBuilder对象,这里用到了建造者设计模式
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    //其中parser.parse()负责解析xml,然后返回configuration对象,接着通过build(configuration)创建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.
    }
  }
}
 
//parse()方法:负责解析xml,并将解析之后的配置封装到configuration中,返回给build方法,用于创建SqlSessionFactory
//org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
public Configuration parse() {
  //判断是否重复解析  
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //解析mybatis-config.xml中<configuration>标签的内容,封装成XNode对象,然后传入parseConfiguration方法进行具体的配置解析
  //parseConfiguration方法负责具体的配置解析
  parseConfiguration(parser.evalNode("/configuration"));
  //返回configuration对象
  return configuration;
}

我们debug看一下,parser.evalNode("/configuration")返回的结果如下图所示:

图片.png

parse()方法里面实际上是调用的parseConfiguration方法,负责解析XML配置。

根据上图configuration标签内我们声明的标签,遍历,挨个进行解析:

//org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
  try {
    //先解析properties标签
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    loadCustomLogImpl(settings);
    //typeAliases别名配置解析, 别名配置有两种方式: a.指定单个实体; b.指定包路径
    //类型别名是为Java类型设置一个短的名字, 用来减少类完全限定名的冗余
    typeAliasesElement(root.evalNode("typeAliases"));
    //插件解析
    pluginElement(root.evalNode("plugins"));
    //对象工厂解析
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    //解析全局配置settings相关配置
    settingsElement(settings);
    // environments里面其实就包含我们的数据库连接信息等的配置,重点,下面详细介绍.
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    //类型转换器相关配置解析,用于数据库类型和Java数据类型的转换
    typeHandlerElement(root.evalNode("typeHandlers"));
    //mapper接口配置解析,重点,下面详细介绍
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}
总结:parseConfiguration完成的是解析configuration下的所有标签,主要包括environment、mapper接口、别名等的配置。

接下来我们先来看看Mybatis如何解析environments标签内容的。

  • environmentsElement(root.evalNode("environments")),其实就是解析environments,如下对应的配置:
<environment id="development">
    <transactionManager type="JDBC"/>
    <dataSource type="POOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://127.0.0.1:3306/user_mybatis?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </dataSource>
</environment>

查看environmentsElement()方法的源码:

//解析environments
private void environmentsElement(XNode context) throws Exception {
  if (context != null) {
    if (environment == null) {
      //development  
      environment = context.getStringAttribute("default");
    }
    //循环遍历XML各个节点
    for (XNode child : context.getChildren()) {
       //获取ID属性,对应<environment id="development">的ID属性, 即id=development
      String id = child.getStringAttribute("id");
      if (isSpecifiedEnvironment(id)) {
        //获取transactionManager事务管理器相关配置
        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对象    
        configuration.setEnvironment(environmentBuilder.build());
      }
    }
  }
}

我们看到,数据源的解析其实是在dataSourceElement()方法中进行的:

//org.apache.ibatis.builder.xml.XMLConfigBuilder#dataSourceElement
//解析dataSource数据源配置
private DataSourceFactory dataSourceElement(XNode context) throws Exception {
  if (context != null) {
    String type = context.getStringAttribute("type");
    //将解析出来的属性内容封装成Properties对象
    Properties props = context.getChildrenAsProperties();
    DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance();
    factory.setProperties(props);
    return factory;
  }
  throw new BuilderException("Environment declaration requires a DataSourceFactory.");
}

如下图,解析出标签内配置的四个properties属性,并封装成Properties对象,赋值给DataSourceFactory,返回DataSourceFactory对象。

图片.png

小总结:dataSource数据源解析大体过程为:

  1. 通过XMLConfigBuilder#environmentsElement方法获取到mybatis-config.xml配置文件中environments标签下的enviroment标签信息;
  2. 然后通过XMLConfigBuilder#dataSourceElement解析dataSource中的内容;
  3. 解析出标签内配置的properties属性,并封装成Properties对象;
  4. 将dataSource中的内容存在在Enviroment的DataSource变量中;
  5. 将环境配置信息读取后存放在Confiuartion变量中;
this.configuration.setEnvironment(environmentBuilder.build());

以上就是关于数据源配置的解析。接下来我们再看一下mapper接口是如何解析的,其他配置的解析感兴趣的小伙伴可以自行Debug分析。

三、Mapper接口解析过程

对mapper接口的解析,对应的解析方法是XMLConfigBuilder的mapperElement(),具体代码如下:

//mapper接口配置解析
//org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    //实际上就是获取到:<mapper resource="mapper/UserMapper.xml"/>
    for (XNode child : parent.getChildren()) {
      //判断是否是指定mapper包扫描配置,我们这是使用的resource配置
      //解析<package name=""/>  
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
         //mapper接口所在的包扫描路径,实际上底层调用mapperRegistry.addMappers(packageName);
        configuration.addMappers(mapperPackage);
      } else {
        //本示例中使用的就是resources方式: resource = mapper/UserMapper.xml
        String resource = child.getStringAttribute("resource");
        //因为我们没有指定url,所以url = null
        String url = child.getStringAttribute("url");
        //因为我们没有指定class,所以mapperClass = null
        String mapperClass = child.getStringAttribute("class");
        //接下来其实就是判断,mybatis-config.xml配置中我们指定的是哪一种方式配置mapper接口
        //主要有四种方式:a.指定包扫描方式  b.指定resource方式  c.指定url方式  d.指定class方式
        if (resource != null && url == null && mapperClass == null) {
          //解析<mapper resource=""></mapper>  
          //b.指定resource方式
          ErrorContext.instance().resource(resource);
          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) {
          //解析<mapper url=""></mapper>
          //c.指定url方式
          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) {
          //解析<mapper class=""></mapper>
          //d.指定class方式
          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方式为例来看一下Mybatis对 Mapper 映射器的解析过程,主要是调用XMLMapperBuilder的parse()方法进行,这里也是使用了构建者模式。

if (resource != null && url == null && mapperClass == null) {
  //拿到mybatis-config.xml中配置的mapper路径:resource="mapper/UserMapper.xml"
  ErrorContext.instance().resource(resource);
  //通过Resources读取转换成资源文件流
  InputStream inputStream = Resources.getResourceAsStream(resource);
  //使用构建者模式,调用XMLMapperBuilder类的parse()方法进行解析
  XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
  mapperParser.parse();
}
 
//拿到mapper.xml文件之后的具体解析方法
//org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    //解析所有的mapper子标签  
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    //绑定mapper接口
    bindMapperForNamespace();
  }
 
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

我们看到,parse()方法先调用了configurationElement()来对具体的mapper子标签内容进行解析,然后调用bindMapperForNamespace()方法进行mapper接口与xml的绑定。

详细代码如下:

//org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement
//configurationElement():解析的是Mapper.xml的标签
private void configurationElement(XNode context) {
  try {
    //获取到namespace命名空间  
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    builderAssistant.setCurrentNamespace(namespace);
    //缓存配置的引用
    cacheRefElement(context.evalNode("cache-ref"));
    //缓存配置
    cacheElement(context.evalNode("cache"));
    //解析parameterMap标签
    parameterMapElement(context.evalNodes("/mapper/parameterMap"));
    //解析resultmap标签, 用来描述如何从数据库结果集中来加载对象
    resultMapElements(context.evalNodes("/mapper/resultMap"));
    //解析sql标签,可被其他语句引用的可重用sql语句块
    sqlElement(context.evalNodes("/mapper/sql"));
    //获得MappedStatement对象(增删改查语句)
    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);
  }
}

上面的代码中,buildStatementFromContext()方法很重要,实际上MappedStatement对象就是在这里面创建的。我们进入buildStatementFromContext方法:

private void buildStatementFromContext(List<XNode> list) {
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  buildStatementFromContext(list, null);
}
 
//org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>, java.lang.String)
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  //list其实就是拿到我们mapper.xml中定义的一个select语句,具体如下图:
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      //解析insert/update/select/delete中的标签
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

statementParser.parseStatementNode()才是具体解析增删改查标签的方法,详细代码如下:

//org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode
//解析insert/update/select/delete中的标签
public void parseStatementNode() {
  //对应我们select标签的id属性,即getById
  //id对应于mapper接口的方法,单个mapper.xml中必须唯一,全路径名加+id作为mappedStatement的ID值,所以必须唯一
  String id = context.getStringAttribute("id");
  //数据库厂商标识
  String databaseId = context.getStringAttribute("databaseId");
 
  if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    return;
  }
 
  Integer fetchSize = context.getIntAttribute("fetchSize");
  Integer timeout = context.getIntAttribute("timeout");
  String parameterMap = context.getStringAttribute("parameterMap");
  //参数类型
  String parameterType = context.getStringAttribute("parameterType");
  Class<?> parameterTypeClass = resolveClass(parameterType);
  //高级结果映射,对应外部resultMap的命名引用
  String resultMap = context.getStringAttribute("resultMap");
  //对应我们select标签的resultType返回类型属性,即com.wsh.mybatis.mybatisdemo.entity.User
  String resultType = context.getStringAttribute("resultType");
  String lang = context.getStringAttribute("lang");
  LanguageDriver langDriver = getLanguageDriver(lang);
 
  //通过ClassLoader装载我们的User类,即com.wsh.mybatis.mybatisdemo.entity.User
  Class<?> resultTypeClass = resolveClass(resultType);
  String resultSetType = context.getStringAttribute("resultSetType");
  //默认Statement的类型为PREPARED:预编译类型的statement
  StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
  ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
  //nodeName=select
  String nodeName = context.getNode().getNodeName();
  //拿到当前sql命令类型是哪一种, 主要有UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH这几种
  //这里我们是SELECT查询
  SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
  boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
  boolean useCache = context.getBooleanAttribute("useCache", isSelect);
  boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
 
  // Include Fragments before parsing
  XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
  includeParser.applyIncludes(context.getNode());
 
  // Parse selectKey after includes and remove them.
  processSelectKeyNodes(id, parameterTypeClass, langDriver);
 
  // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
  //创建SqlSource
  SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
  String resultSets = context.getStringAttribute("resultSets");
  String keyProperty = context.getStringAttribute("keyProperty");
  String keyColumn = context.getStringAttribute("keyColumn");
  KeyGenerator keyGenerator;
  String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
  keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
  if (configuration.hasKeyGenerator(keyStatementId)) {
    keyGenerator = configuration.getKeyGenerator(keyStatementId);
  } else {
    keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
        configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
        ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
  }
 
  //创建MappedStatement对象,并存入configuration中
  //MappedStatement其实就是每一个增删改查的标签的里的数据
  //最终存放到mappedStatements中,mappedStatements存放的是一个个的增删改查
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
      fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
      resultSetTypeEnum, flushCache, useCache, resultOrdered,
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}

我们看到,parseStatementNode()方法前面都是对select标签体能设置的一些属性的解析,在方法的最后面,调用了addMappedStatement(),看名字,我们也能够猜到MappedStatement就是在这方法里面创建的。

//org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement(java.lang.String, org.apache.ibatis.mapping.SqlSource, org.apache.ibatis.mapping.StatementType, org.apache.ibatis.mapping.SqlCommandType, java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Class<?>, java.lang.String, java.lang.Class<?>, org.apache.ibatis.mapping.ResultSetType, boolean, boolean, boolean, org.apache.ibatis.executor.keygen.KeyGenerator, java.lang.String, java.lang.String, java.lang.String, org.apache.ibatis.scripting.LanguageDriver, java.lang.String)
public MappedStatement addMappedStatement(
    String id,
    SqlSource sqlSource,
    StatementType statementType,
    SqlCommandType sqlCommandType,
    Integer fetchSize,
    Integer timeout,
    String parameterMap,
    Class<?> parameterType,
    String resultMap,
    Class<?> resultType,
    ResultSetType resultSetType,
    boolean flushCache,
    boolean useCache,
    boolean resultOrdered,
    KeyGenerator keyGenerator,
    String keyProperty,
    String keyColumn,
    String databaseId,
    LanguageDriver lang,
    String resultSets) {
 
  if (unresolvedCacheRef) {
    throw new IncompleteElementException("Cache-ref not yet resolved");
  }
 
  id = applyCurrentNamespace(id, false);
  boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
 
  //将解析出来的标签属性内容设置到MappedStatement对象中
  MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
      .resource(resource)
      .fetchSize(fetchSize)
      .timeout(timeout)
      .statementType(statementType)
      .keyGenerator(keyGenerator)
      .keyProperty(keyProperty)
      .keyColumn(keyColumn)
      .databaseId(databaseId)
      .lang(lang)
      .resultOrdered(resultOrdered)
      .resultSets(resultSets)
      .resultMaps(getStatementResultMaps(resultMap, resultType, id))
      .resultSetType(resultSetType)
      .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
      .useCache(valueOrDefault(useCache, isSelect))
      .cache(currentCache);
 
  ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
  if (statementParameterMap != null) {
    statementBuilder.parameterMap(statementParameterMap);
  }
  //使用构建者模式构建MappedStatement
  MappedStatement statement = statementBuilder.build();
  //将MappedStatement添加到configuration对象中
  configuration.addMappedStatement(statement);
  return statement;
}

继续查看configuration.addMappedStatement(statement)方法的实现:

//org.apache.ibatis.session.Configuration#addMappedStatement
public void addMappedStatement(MappedStatement ms) {
  mappedStatements.put(ms.getId(), ms);
}

Debug图如下所示:

图片.png

其中Map<String, MappedStatement> mappedStatements是configuration类中的一个成员属性,里面存放着我们所有mapper接口以及对应的SQL信息的绑定信息。为什么使用map来存放,其实就是为了后面执行具体的mapper方法的时候,从mappedStatements根据【namespace+方法名称】作为key,从mappedStatements中进行获取到MappedStatement对象,进而就能获取到对应的SQL已经参数信息,自然就能执行SQL了,以上就是MappedStatement的生成过程。

四、Mapper接口与MapperProxyFactory的绑定过程

前面我们分析了XMLMapperBuilder的parse()方法对mapper接口的解析过程,在执行完configurationElement()方法后还调用了bindMapperForNamespace()方法将mapper接口与MapperProxyFactory绑定绑定的过程。

下面我们看一下bindMapperForNamespace()的源码:

//org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace
private void bindMapperForNamespace() {
  //获取到当前mapper接口的命名空间:com.wsh.mybatis.mybatisdemo.mapper.UserMapper
  String namespace = builderAssistant.getCurrentNamespace();
  if (namespace != null) {
    Class<?> boundType = null;
    try {
      //通过classloader生成对应的Class对象
      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接口,将mapper接口添加到configuration全局配置中
        configuration.addMapper(boundType);
      }
    }
  }
}

可以看到,真正调用的是configuration.addMapper()方法:

//org.apache.ibatis.session.Configuration#addMapper
public <T> void addMapper(Class<T> type) {
  //mapperRegistry是Configuration类的一个成员属性
  //protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  mapperRegistry.addMapper(type);
}
 
//org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) {
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      //将mapper接口作为key,存入knownMappers中
      //knownMappers是一个map, Map<Class<?>, MapperProxyFactory<?>> knownMappers
      //接口类型(key)->工厂类的映射关系
      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);
      }
    }
  }
}

Debug观察一下:

图片.png

前面我们分析了数据源怎么解析的,还有mapper接口是如何解析的,并且如何生成MappedStatement的,分析完后,我们回到最开始解析XML那里,执行完parseConfiguration(),已经把解析出来的配置都封装进Configuration 对象了,所以XMLConfigBuilder的parse()方法执行完成之后,会返回configuration对象,然后传入SqlSessionFactoryBuilder#build(org.apache.ibatis.session.Configuration)中构建SqlSessionFactory对象,具体代码如下:

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

如下图,可以看到,所有相关配置解析出来之后都保存在了SqlSessionFactory 的成员变量configuration中,然后通过configuration创建了DefaultSqlSessionFactory对象。至此,我们的SqlSessionFactory对象就算构建成功了。

图片.png

小总结:

XMLMapperBuilder.parse()方法,是对 Mapper接口的解析,里面有两个方法:

  • (1)configurationElement():解析所有的子标签,最终解析Mapper.xml中的insert/update/delete/select标签的id(全路径)组成key和整个标签和数据连接组成MappedStatement存放到Configuration中的 mappedStatements这个map里面;
  • (2)bindMapperForNamespace():是把mapper接口和MapperProxyFactory绑定起来,然后存放在MapperRegistry中的knownMappers成员属性里面;