前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MyBatis源码阅读(三) --- 配置信息的解析以及SqlSessionFactory构建过程

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

作者头像
终有救赎
发布2023-12-22 14:20:42
1190
发布2023-12-22 14:20:42
举报
文章被收录于专栏:多线程多线程
一、简介

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

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

二、配置信息解析过程

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

代码语言:javascript
复制
//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配置类。

代码语言:javascript
复制
//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
图片.png

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

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

代码语言:javascript
复制
//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,如下对应的配置:
代码语言:javascript
复制
<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()方法的源码:

代码语言:javascript
复制
//解析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()方法中进行的:

代码语言:javascript
复制
//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
图片.png

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

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

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

三、Mapper接口解析过程

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

代码语言:javascript
复制
//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()方法进行,这里也是使用了构建者模式。

代码语言:javascript
复制
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的绑定。

详细代码如下:

代码语言:javascript
复制
//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方法:

代码语言:javascript
复制
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()才是具体解析增删改查标签的方法,详细代码如下:

代码语言:javascript
复制
//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就是在这方法里面创建的。

代码语言:javascript
复制
//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)方法的实现:

代码语言:javascript
复制
//org.apache.ibatis.session.Configuration#addMappedStatement
public void addMappedStatement(MappedStatement ms) {
  mappedStatements.put(ms.getId(), ms);
}

Debug图如下所示:

图片.png
图片.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()的源码:

代码语言:javascript
复制
//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()方法:

代码语言:javascript
复制
//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
图片.png

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

代码语言:javascript
复制
public SqlSessionFactory build(Configuration config) {
  return new DefaultSqlSessionFactory(config);
}

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

图片.png
图片.png

小总结:

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

  • (1)configurationElement():解析所有的子标签,最终解析Mapper.xml中的insert/update/delete/select标签的id(全路径)组成key和整个标签和数据连接组成MappedStatement存放到Configuration中的 mappedStatements这个map里面;
  • (2)bindMapperForNamespace():是把mapper接口和MapperProxyFactory绑定起来,然后存放在MapperRegistry中的knownMappers成员属性里面;
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-12-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、简介
  • 二、配置信息解析过程
  • 三、Mapper接口解析过程
  • 四、Mapper接口与MapperProxyFactory的绑定过程
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档