0
点赞
收藏
分享

微信扫一扫

MyBatis源码分析(二)SqlSessionFactory的构建及配置文件读取过程


文章目录

  • 一、MyBatis配置文件
  • 二、SqlSessionFactory的获取
  • 1、初始化XML配置的Document以及其他对象
  • 2、解析配置文件
  • (1)配置Environment
  • (2)存放Mapper
  • (3)解析Mapper
  • 3、构造SqlSessionFactory
  • 4、总结
  • 未完待续

一、MyBatis配置文件

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:

MyBatis源码分析(二)SqlSessionFactory的构建及配置文件读取过程_mysql


MyBatis的Configuration类是一个非常重要的类,它相当于Spring的ApplicationContext,里面包含着所有的配置与属性信息。

public class Configuration {

	protected Environment environment;
	// 允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。默认为false
	protected boolean safeRowBoundsEnabled;
	// 允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false
	protected boolean safeResultHandlerEnabled = true;
	// 是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN
	
	// 到经典 Java 属性名 aColumn 的类似映射。默认false
	protected boolean mapUnderscoreToCamelCase;
	// 当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载。默认值false (true in ≤3.4.1)
	protected boolean aggressiveLazyLoading;
	// 是否允许单一语句返回多结果集(需要兼容驱动)。
	protected boolean multipleResultSetsEnabled = true;
	// 允许 JDBC 支持自动生成主键,需要驱动兼容。这就是insert时获取mysql自增主键/oracle sequence的开关。
	// 注:一般来说,这是希望的结果,应该默认值为true比较合适。
	protected boolean useGeneratedKeys;
	// 使用列标签代替列名,一般来说,这是希望的结果
	protected boolean useColumnLabel = true;
	// 是否启用缓存
	protected boolean cacheEnabled = true;
	// 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,
	// 这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。
	protected boolean callSettersOnNulls;
	// 允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,
	// 并且加上-parameters选项。(从3.4.1开始)
	protected boolean useActualParamName = true;
	//当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。
	// 请注意,它也适用于嵌套的结果集 (i.e. collectioin and association)。(从3.4.2开始)
	// 注:这里应该拆分为两个参数比较合适, 一个用于结果集,一个用于单记录。
	// 通常来说,我们会希望结果集不是null,单记录仍然是null
	protected boolean returnInstanceForEmptyRow;
	protected boolean shrinkWhitespacesInSql;
	// 指定 MyBatis 增加到日志名称的前缀。
	protected String logPrefix;
	// 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。一般建议指定为slf4j或log4j
	protected Class<? extends Log> logImpl;
	// 指定VFS的实现, VFS是mybatis提供的用于访问AS内资源的一个简便接口
	protected Class<? extends VFS> vfsImpl;
	protected Class<?> defaultSqlProviderType;
	// MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。
	// 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。
	// 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
	protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
	// 当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,
	// 多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。
	protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
	// 指定对象的哪个方法触发一次延迟加载。
	protected Set<String> lazyLoadTriggerMethods = new HashSet<>(Arrays.asList("equals", "clone", "hashCode", "toString"));
	// 设置超时时间,它决定驱动等待数据库响应的秒数。默认不超时
	protected Integer defaultStatementTimeout;
	// 为驱动的结果集设置默认获取数量。
	protected Integer defaultFetchSize;
	// SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements);
	// BATCH 执行器将重用语句并执行批量更新。
	protected ResultSetType defaultResultSetType;
	// 默认执行器类型
	protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
	// 指定 MyBatis 应如何自动映射列到字段或属性。
	// NONE 表示取消自动映射;
	// PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。
	// FULL 会自动映射任意复杂的结果集(无论是否嵌套)。
	protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
	// 指定发现自动映射目标未知列(或者未知属性类型)的行为。这个值应该设置为WARNING比较合适
	protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior =
	AutoMappingUnknownColumnBehavior.NONE;
	// settings下的properties属性
	protected Properties variables = new Properties();
	// 默认的反射器工厂,用于操作属性、构造器方便
	protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
	// 对象工厂, 所有的类resultMap类都需要依赖于对象工厂来实例化
	protected ObjectFactory objectFactory = new DefaultObjectFactory();
	// 对象包装器工厂,主要用来在创建非原生对象,比如增加了某些监控或者特殊属性的代理类
	protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
	// 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。特定关联关系中可通过设置fetchType属性来覆盖该项的开关状态
	protected boolean lazyLoadingEnabled = false;
	// 指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。MyBatis 3.3+使用JAVASSIST
	protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL
	// MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
	protected String databaseId;
	/**
	* Configuration factory class.
	* Used to create Configuration for loading deserialized unread properties.
	*
	* @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
	*/
	protected Class<?> configurationFactory;
	
	protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
	// mybatis插件列表
	protected final InterceptorChain interceptorChain = new InterceptorChain();
	protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
	// 类型注册器, 用于在执行sql语句的出入参映射以及mybatis-config文件里的各种配置
	// 比如<transactionManager type="JDBC"/><dataSource type="POOLED">时使用简写
	protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
	protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
    protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
	protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
	protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
	protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
	protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
	protected final Set<String> loadedResources = new HashSet<>();
	protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous
	mappers");
	protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
	protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
	protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
	protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
	/*
	* A map holds cache-ref relationship. The key is the namespace that
	* references a cache bound to another namespace and the value is the
	* namespace which the actual cache is bound to.
	*/
	protected final Map<String, String> cacheRefMap = new HashMap<>();
	
	public Configuration(Environment environment) {
		this();
		this.environment = environment;
	}

MyBatis在SqlSessionFactory创建的过程中,就已经将所有的配置、Mapper等信息全部解析完毕,那么它到底是如何解析的呢?

二、SqlSessionFactory的获取

  • SqlSessionFactory是MyBatis的关键对象,它是个单个数据库映射关系经过编译后的内存镜像。
  • SqlSessionFactory对象的实例可以通过SqlSessionFactoryBuilder对象类获得,而SqlSessionFactoryBuilder则可以从XML配置文件或一个预先定制的Configuration的实例构建出SqlSessionFactory的实例。
  • 每一个MyBatis的应用程序都以一个SqlSessionFactory对象的实例为核心。
  • SqlSessionFactory是线程安全的,SqlSessionFactory一旦被创建,应该在应用执行期间都存在。在应用运行期间不要重复创建多次,建议使用单例模式。
  • SqlSessionFactory是创建SqlSession的工厂。

在官方文档中,提供了两种方式获取SqlSessionFactory,一种是XML配置的方式,我们将核心代码贴出来:

// 使用XML方式获取SqlSessionFactory
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

配置文件如下:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "https://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <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>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

另一种是通过API编程的方式创建SqlSessionFactory:

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

我们发现,这种方式与上面的XML配置文件的方式异曲同工,只不过是DataSource交由用户自己来创建,并且Environment的创建等等,与XML方式的解析配置文件并创建的方式惊人的相似!

所以,XML配置的方式相当于是MyBatis提供的一种方便的配置方法,如果知道其核心原理,是可以完全自己通过API来定制。

我们就XML配置的方式,对SqlSessionFactory的创建流程进行解读。

1、初始化XML配置的Document以及其他对象

我们发现,核心逻辑其实就是读取XML配置文件的输入流,然后调用SqlSessionFactoryBuilder的build方法:

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

默认的environment和Properties是null。在new XMLConfigBuilder的过程其实做了很多事:

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) {
  super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  this.configuration.setVariables(props);
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}

我们发现,在构造方法中初始化了Configuration,并且new了一个XPathParser,顾名思义,是用来解析XML的。

在XPathParser构造方法中,调用了createDocument方法:

public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
  commonConstructor(validation, variables, entityResolver); // 生产了一个XPath
  this.document = createDocument(new InputSource(inputStream));
}

// org.apache.ibatis.parsing.XPathParser#createDocument
private Document createDocument(InputSource inputSource) {
  // important: this must only be called AFTER common constructor
  try {
  	// java自带的,设置一堆属性
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setValidating(validation);// 校验

    factory.setNamespaceAware(false); // namespace
    factory.setIgnoringComments(true); // 忽略注释
    factory.setIgnoringElementContentWhitespace(false); // 跳过空格
    factory.setCoalescing(false);
    factory.setExpandEntityReferences(true);

    DocumentBuilder builder = factory.newDocumentBuilder();
    builder.setEntityResolver(entityResolver);
    builder.setErrorHandler(new ErrorHandler() {
      @Override
      public void error(SAXParseException exception) throws SAXException {
        throw exception;
      }

      @Override
      public void fatalError(SAXParseException exception) throws SAXException {
        throw exception;
      }

      @Override
      public void warning(SAXParseException exception) throws SAXException {
      }
    });
    // JDK自带的解析XML方式,生成一个Document
    return builder.parse(inputSource);
  } catch (Exception e) {
    throw new BuilderException("Error creating document instance.  Cause: " + e, e);
  }
}

此时,我们已经将XML配置文件,解析成Document了,同时初始化了XPathParser等一些用于解析XML的类。

2、解析配置文件

此时我们再次回到SqlSessionFactoryBuilder的build方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    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.
    }
  }
}

调用了XMLConfigBuilder的parse方法:

// org.apache.ibatis.builder.xml.XMLConfigBuilder#parse
public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

顾名思义,是解析configuration标签的,使用JDK自带的解析器,对标签进行解析,最终将解析出的内容封装到Configuration对象中:

// org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) {
  try {
    //issue #117 read properties first
    propertiesElement(root.evalNode("properties"));
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    loadCustomVfs(settings);
    typeAliasesElement(root.evalNode("typeAliases"));
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    settingsElement(settings);
    // read it after objectFactory and objectWrapperFactory issue #631
    // 解析environment标签,创建数据源,并且创建Environment
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // 解析typeHandlers标签,注册typeHandler
    typeHandlerElement(root.evalNode("typeHandlers"));
    // 解析mappers标签,注册Mappers
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

到此为止,就是将XML配置文件的配置,全部解析并处理完成了。

我们挑几个重点的配置,来分析一下是怎么处理的。

(1)配置Environment

解析environment标签是以下逻辑:

// org.apache.ibatis.builder.xml.XMLConfigBuilder#environmentsElement
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());
      }
    }
  }
}

我们发现,是解析了dataSource并创建了一个数据源,将数据源和事务管理器放入了Environment。

最终将创建的Environment放入了Configuration中。

(2)存放Mapper

对mapper标签处理逻辑如下:

// org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
      	// 解析整个包,获取<mappers>标签的子标签
      	// 获取mapper接口和mapper映射文件对应的package包名
        String mapperPackage = child.getStringAttribute("name");
        // 将包下所有的mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
        configuration.addMappers(mapperPackage);
      } else {// <mapper>子标签
      	// 获取<mapper>子标签的resource属性
        String resource = child.getStringAttribute("resource");
        // 获取<mapper>子标签的url属性
        String url = child.getStringAttribute("url");
        // 获取<mapper>子标签的class属性
        String mapperClass = child.getStringAttribute("class");
        // 它们是互斥的
        if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // 专门用来解析mapper映射文件
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          // 通过XMLMapperBuilder解析mapper映射文件
          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());
          // 通过XMLMapperBuilder解析mapper映射文件
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          Class<?> mapperInterface = Resources.classForName(mapperClass);
          // 将指定mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
          configuration.addMapper(mapperInterface);
        } else {
          throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
        }
      }
    }
  }
}

我们可以看出,解析mapper的过程分两种,一种是指定包名的,需要解析包下所有的mapper类。另一种是指定resource的,只需要直接将该类调用configuration.addMapper将Mapper保存。

// org.apache.ibatis.binding.MapperRegistry#addMappers(java.lang.String, java.lang.Class<?>)
// 处理包下所有接口,并保存Mapper
public void addMappers(String packageName, Class<?> superType) {
  ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
  resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
  Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
  for (Class<?> mapperClass : mapperSet) {
    addMapper(mapperClass);
  }
}

下面是保存Mapper接口的逻辑,我们发现只会处理接口,并且会保存在knownMappers中,这是一个HashMap。

// org.apache.ibatis.binding.MapperRegistry#addMapper
public <T> void addMapper(Class<T> type) {
  if (type.isInterface()) {
    // 如果Map集合中已经有该mapper接口的映射,就不需要再存储了
    if (hasMapper(type)) {
      throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
    }
    boolean loadCompleted = false;
    try {
      // 将mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
      knownMappers.put(type, new MapperProxyFactory<T>(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.映射器解析器。如果类型已经已知,则不会尝试。
      // 用来解析注解方式的mapper接口
      MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
      // 解析注解方式的mapper接口
      parser.parse();
      loadCompleted = true;
    } finally {
      if (!loadCompleted) {
        knownMappers.remove(type);
      }
    }
  }
}

在MapperAnnotationBuilder创建的过程中,此时还会初始化resource数据,将class的name进行替换:

public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
  String resource = type.getName().replace('.', '/') + ".java (best guess)";
  this.assistant = new MapperBuilderAssistant(configuration, resource);
  this.configuration = configuration;
  this.type = type;
}

其中,parser.parse()的过程中会进行以下处理:

// org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#parse
public void parse() {
  String resource = type.toString();
  if (!configuration.isResourceLoaded(resource)) {
    loadXmlResource(); // 解析Mapper.xml资源 见 (3)
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());
    parseCache(); // 解析Cache缓存相关
    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 {
        parseStatement(method);// 处理注解(@Select、@Update等)的 Statement
      } catch (IncompleteElementException e) {
        configuration.addIncompleteMethod(new MethodResolver(this, method));
      }
    }
  }
  // 解析失败之后,会重新执行一次
  parsePendingMethods();
}

(3)解析Mapper

我们继续接着 (2) 继续往下分析。
loadXmlResource方法其中解析Mapper.xml的流程如下,我们发现会根据接口的路径,查找到Mapper的xml路径,并进行解析。

// org.apache.ibatis.builder.annotation.MapperAnnotationBuilder#loadXmlResource
private void loadXmlResource() {
  // Spring may not know the real resource name so we check a flag
  // to prevent loading again a resource twice
  // this flag is set at XMLMapperBuilder#bindMapperForNamespace
  if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
    String xmlResource = type.getName().replace('.', '/') + ".xml";
    // #1347
    InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
    if (inputStream == null) {
      // Search XML mapper that is not in the module but in the classpath.
      try {
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e2) {
        // ignore, resource is not required
      }
    }
    if (inputStream != null) {
      XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
      xmlParser.parse();
    }
  }
}

而xmlParser.parse()方法,会执行以下逻辑:

// org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
public void parse() {
  // 判断资源是否已被加载
  if (!configuration.isResourceLoaded(resource)) {
    // 处理mapper标签,包括namespace、cache-ref、cache、parameterMap、resultMap、sql、select|insert|update|delete等
    configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);
    bindMapperForNamespace();
  }
  // 处理Mapper.xml中的ResultMap
  parsePendingResultMaps();
  // 处理缓存
  parsePendingCacheRefs();
  // 综合处理准备Statement
  parsePendingStatements();
}

其中,bindMapperForNamespace方法,会将资源添加到loadedResources。并且最后一步,通过Mapper.xml的namespace获取到接口的路径,会再次调用configuration.addMapper,

// 
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 && !configuration.hasMapper(boundType)) {
      // Spring may not know the real resource name so we set a flag Spring可能不知道真正的资源名,所以我们设置了一个标志
      // to prevent loading again this resource from the mapper interface 防止从mapper接口再次加载此资源
      // look at MapperAnnotationBuilder#loadXmlResource 查看MapperAnnotationBuilder#loadXmlResource
      configuration.addLoadedResource("namespace:" + namespace);
      configuration.addMapper(boundType); // 都做了重复判断,不会重复加载
    }
  }
}

3、构造SqlSessionFactory

此时我们再次回到SqlSessionFactoryBuilder的build方法:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    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.
    }
  }
}

调用build方法,传入一个Configuration:

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

public DefaultSqlSessionFactory(Configuration configuration) {
  this.configuration = configuration;
}

将Configuration 中的各种配置信息,存储到DefaultSqlSessionFactory中。

4、总结

MyBatis源码分析(二)SqlSessionFactory的构建及配置文件读取过程_XML_02


当我们获取到SqlSessionFactory之后,相当于初始化工作已经全部处理完成了,DefaultSqlSessionFactory中包含着Configuration的引用,所有的配置(Mapper、TypeHandler等等)都加载在Configuration中了。

MyBatis源码分析(二)SqlSessionFactory的构建及配置文件读取过程_mysql


具体配置请移步:

https://mybatis.org/mybatis-3/zh/configuration.html

未完待续


举报

相关推荐

0 条评论