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

news2024/11/16 7:28:12

文章目录

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

一、MyBatis配置文件

MyBatis 的配置文件包含了会深深影响 MyBatis 行为的设置和属性信息。 配置文档的顶层结构如下:
在这里插入图片描述
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、总结

在这里插入图片描述
当我们获取到SqlSessionFactory之后,相当于初始化工作已经全部处理完成了,DefaultSqlSessionFactory中包含着Configuration的引用,所有的配置(Mapper、TypeHandler等等)都加载在Configuration中了。
在这里插入图片描述
具体配置请移步:
https://mybatis.org/mybatis-3/zh/configuration.html

未完待续

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/381420.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

测试2年,当初一起入行的朋友很多月薪20k了,自己却还没过万,到底差在了哪里?

说来奇怪&#xff0c;不管是读书还是工作&#xff0c;都存在一个现象&#xff0c;那就是人比人&#xff0c;比死人。读书的时候&#xff0c;不管是老师还是家长口中&#xff0c;总会有一个“别人家的孩子”。同样&#xff0c;到工作中&#xff0c;领导口中总会有一个“别人的员…

Doxygen 详细使用

doxygen的安装和基本使用可参考&#xff1a;Doxygen的安装和基本使用常用选项doxygen的所有选项的参考文档&#xff1a;doxygen官网文档2、样式说明doxygen可以自己自定义样式。手写 .css文件 &#xff08;可以查看doxygen的源码&#xff0c;进行相对应修改&#xff09;在Exper…

推荐一个.Net Core开发的蜘蛛爬虫开源项目

更多开源项目请查看&#xff1a;一个专注推荐.Net开源项目的榜单 如果我们需要抓取网络上的数据&#xff0c;这时候我们就要写爬虫&#xff0c;这里面就涉及到网页的抓取、以及网页分析与数据提取、抓取的性能等知识&#xff0c;今天就给大家推荐一个开源项目&#xff0c;它可以…

java基础复习(练习写博客)

文章目录Java特性和优势java三大版本JDK、JRE、JVM(从前到后包含)Java开发环境搭建步骤Java程序运行机制IDEJava基础语法一、注释、标识符、关键字二、数据类型&#xff08;shift双击问题&#xff09;三、变量、常量、作用域四、运算符五、包机制、JavaDocJava进阶语法一、Scan…

【Java】代码中的安全漏洞解决合集(更新中)

汝之观览&#xff0c;吾之幸也&#xff01;本文主要讲解Java的一些安全漏洞&#xff0c;并且给出浅知的解决方案。 具体国内的风险可查看网址工业和信息化部网络安全威胁和漏洞信息共享平台 1、Spring Framework反射型文件下载漏洞&#xff08;CVE-2020-5421&#xff09; 漏洞…

Linux 进程:fork()与vfork()的对比

目录一、fork函数二、vfork函数1.函数的原理2.函数的隐患3.解决函数隐患的方法在Linux的进程学习中&#xff0c;常使用fork函数来创建子进程&#xff0c;但其实还有一个vfork函数也可以创建子进程。但是这两个函数的实现机制不同&#xff0c;fork函数使用了写实拷贝技术&#x…

Ubuntu 20搭建srs3.0

SRS官网&#xff0c;v3Home介绍&#xff0c;部署帮助。 1.创建一个srs项目文件夹 2.进入后用git命令拉取3.0release版本&#xff1a;git clone -b 3.0release https://gitee.com/ossrs/srs.git 3.进入srs/trunk目录打开终端输入命令&#xff1a;./configure 4.继续输入命令&a…

Python+tkinter添加滚动条

大家好&#xff0c;我是IKUN的真爱粉&#xff0c;有时候我们需要在tkinter上加滚动条&#xff0c;那么怎么制作呢&#xff0c;我们先看下面的视频展示效果&#xff0c;是不是你想要的 展示 感觉制作的略微粗糙&#xff0c;各位可以后期自己慢慢调整 创建滚动条重要的步骤是&a…

【C++进阶】四、STL---set和map的介绍和使用

目录 一、关联式容器 二、键值对 三、树形结构的关联式容器 四、set的介绍及使用 4.1 set的介绍 4.2 set的使用 五、multiset的介绍及使用 六、map的介绍和使用 6.1 map的介绍 6.2 map的使用 七、multimap的介绍和使用 一、关联式容器 前面已经接触过 STL 中的部分…

SAP 详解ST02

问&#xff1a;在st02中看到&#xff0c;Program和Export/Import的Swap出现红的了&#xff0c;这个是什么原因啊&#xff0c;是不是对系统的性能有影响啊&#xff0c;是否应该调整一些参数啊。要怎么调整呢&#xff1f; 复1&#xff1a;双击红色的部分就可以看到相应的参数修改…

【2023/图对比/无负样本】基于无负样本损失和自适应增强的图对比学习

如果觉得我的分享有一定帮助&#xff0c;欢迎关注我的微信公众号 “码农的科研笔记”&#xff0c;了解更多我的算法和代码学习总结记录。或者点击链接扫码关注【2023/图对比/无负样本】基于无负样本损失和自适应增强的图对比学习 周天琪,杨艳,张继杰等.基于无负样本损失和自适应…

CCNP350-401学习笔记(601-650题)

601、What is a characteristic of Cisco StackWise technology? A. It supports devices that are geographically separated. B. It is supported on the Cisco 4500 series. C. It combines exactly two devices.D. It uses proprietary cabling 602、Refer to the exhibi…

低代码开发平台真的靠谱吗?

低代码开发平台真的靠谱吗&#xff1f;这么跟你说吧&#xff1a; 你想用美图秀秀实现PS的修图效果但失败了&#xff0c;转头就说美图秀秀垃圾&#xff0c;是不是无理取闹你想用剪映实现PR的视频剪辑效果但失败了&#xff0c;转头就说剪映啥也不是&#xff0c;是不是在无理取闹…

操作指南:如何高效使用Facebook Messenger销售(一)

在销售方面&#xff0c; Facebook Messenger 是许多各种规模的企业的首选渠道。这篇文章将向您介绍使用 Messenger 作为销售渠道。我们还将指导您完成用智能客服工具SaleSmartly(ss客服)将您的 Facebook Messenger 销售更上一层楼。配图来源&#xff1a;SaleSmartly&#xff08…

JS#2 对象

一. Array对象定义var 变量名 new Array(元素列表);var 变量名 [元素列表];访问arr[索引] 值;注意JS数组类似于Java的集合, 长度, 类型都可变常用的属性和方法属性: length 数组元素的个数方法: push( ) 添加元素splice( ) 删除元素代码: <!DOCTYPE html> <html la…

day55-day56【代码随想录】二刷数组

文章目录前言一、字符串的排列&#xff08;力扣567&#xff09;【滑动窗口】二、找到字符串中所有字母异位词&#xff08;力扣438&#xff09;【滑动窗口】三、串联所有单词的子串&#xff08;力扣30&#xff09;【滑动窗口】****【hard】每日一题day55&#xff1a;合并相似的物…

面试题HTML篇(一)

目录 一、meta 标签可以做什么 四、行内元素、块级元素、空元素 元素之间的转换问题&#xff1a; 五、px,em,rem,vw,vh,rpx等单位的特性 六、替换元素和非替换元素 七、first-of-type和first-child有什么区别 八、doctype标签的作用 九、link标签和import标签的区别 十…

import “cv2“ could not be resolved pylance(reportMissingImports)

openCV系列文章目录 文章目录openCV系列文章目录前言一、错误原因二、解决方法1.在vscode&#xff1a;Python:Select Interpreter2.依然报错&#xff1a;cv2.error: OpenCV(4.7.0) D:\a\opencv-python\opencv-python\opencv\modules\highgui\src\window.cpp:1272: error: (-2:U…

InstructGPT论文精读

论文链接&#xff1a;https://arxiv.org/pdf/2203.02155.pdf 1 摘要 做的事&#xff1a; 1、标注了数据&#xff0c;问题和答案写出来&#xff0c;然后训练模型 2、收集数据集&#xff0c;排序模型的输出&#xff0c;使用强化学习训练这个排序的过程 效果层面来说&#xff1…

vant-list使用,请求接口之后会多几次load加页面(详细解释,动图演示)

页面实现效果&#xff1a;在页面中使用了van-tabs组件和van-list组件来实现页面布局和功能。问题描述&#xff1a;在第一个标签下&#xff0c;向下滚动page超过了2页之后&#xff0c;有点击tab切换标签&#xff0c;接口调用了多回。解决问题关键&#xff1a;loading和finished在…