Mybatis踩坑第八弹-运行原理之SqlSessionFactory

大家在学会了如何使用 Mybatis 之后,有没有这样一个疑问——Mybatis 的运行原理到底怎么样的?在看不见的底层,它是如何实现执行增删改查工作的呢?

通常情况下,使用 Mybatis 会有四个步骤:(1)获取 SqlSessionFactory,(2)通过 SqlSessionFactory 获取 SqlSession,(3)通过 SqlSession 获取 数据对象的 Mapper 代理对象,(4)通过 Mapper 代理对象执行各种操作。那么要想搞清楚 Mybatis 的运行原理,就只需要弄懂这四个步骤分别做来了什么事情。

今天主要探索第一个步骤,即获取 SqlSessionFactory。那么首先看一下获取 SqlSessionFactory 的代码,如下:

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

首先把 Mybatis 的全局配置文件 mybatis-config.xml 作为输入流读取进来,然后将输入流作为参数传入了 SqlSessionFactoryBuilder 对象的 build()方法中。下面来看 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()方法中,首先创建了一个 XMLConfigBuilder 的对象,传入了上文中文件流参数 inputStream,环境参数 environment,属性参数 properties;然后执行该对象的 parse()方法,并将返回值作为参数传入重载的 build()方法中;最后返回结果。下面来看一下重载的build()方式是什么样的:

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

通过参数,可以说明 parse()方法返回的是一个 Configuration 对象,而且通过 return ,可以知道最终我们得到的是一个 带有 configuraion 属性的DefaultSqlSessionFactory 对象。

我们再回过头来看一下 parse()方法到底做了什么事情,源码实现:

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

可以看见在方法内部调用了类的内部方法 parseConfiguration(),并且将parser.evalNode("/configuration") 得到的根节点对象传入作为参数,最后返回的是 configuration 对象,继续看 parseConfiguration()的源码实现:

 private void parseConfiguration(XNode root) {
    try {
     Properties settings = settingsAsPropertiess(root.evalNode("settings"));
 //issue #117 read properties first
 propertiesElement(root.evalNode("properties"));
 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
 environmentsElement(root.evalNode("environments"));
 databaseIdProviderElement(root.evalNode("databaseIdProvider"));
 typeHandlerElement(root.evalNode("typeHandlers"));
 mapperElement(root.evalNode("mappers"));
 } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
 }
}

方法内部对根节点标签下面的每一个标签作解析,并将解析到的子节点传入对应的方法,在方法中将标签中的值解析出来并存入 settings 对象,再调用内部方法 settingsElement(settiings)和 mapperElement(),看一下 settingElement()源码:

private void settingsElement(Properties props) throws Exception {
  configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
 configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
 configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
 configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
 configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
 configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
 configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
 configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
 configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
 configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
 configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
 configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
 configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
 configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
 configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
 configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
 configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
 configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
 configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
 configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
 configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), false));
 configuration.setLogPrefix(props.getProperty("logPrefix"));
 @SuppressWarnings("unchecked")
  Class logImpl = (Class)resolveClass(props.getProperty("logImpl"));
 configuration.setLogImpl(logImpl);
 configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory"))); }

所有解析出来的配置信息都从 props 对象中取出,放进了 configuration 中。我们可以看见,每一个属性都有对应的默认值;接下来我们看一下 mapperElament()的源码:

private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String mapperPackage = child.getStringAttribute("name");
 configuration.addMappers(mapperPackage);
 } else {
        String resource = child.getStringAttribute("resource");
 String url = child.getStringAttribute("url");
 String mapperClass = child.getStringAttribute("class");
 if (resource != null && url == null && mapperClass == null) {
          ErrorContext.instance().resource(resource);
 InputStream inputStream = Resources.getResourceAsStream(resource);
 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
 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());
 mapperParser.parse();
 } else if (resource == null && url == null && mapperClass != null) {
          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.");
 }
      }
    }
  }
}

这里会分两种情况,如果在 mappers 标签中使用的是 package 标签,会将标签中 name 属性的值取出;反之,如果使用的是 mapper 标签,会将标签中 resource 属性、url 属性、class 属性的值取出;然后根据相应的值得到 mapper.xml 文件的输入流,将输入流传入 XMLMapperBuilder 的构造函数创建一个对象并调用 parse()方法,看一下它的代码实现:

public void parse() {
  if (!configuration.isResourceLoaded(resource)) {
    configurationElement(parser.evalNode("/mapper"));
 configuration.addLoadedResource(resource);
 bindMapperForNamespace();
 }

  parsePendingResultMaps();
 parsePendingChacheRefs();
 parsePendingStatements(); }

首先拿到 mapper.xml 文件中的 mapper 标签,然后调用configurationElement(parser.evalNode(“/mapper”)),看一下这个方法的实现:

private void configurationElement(XNode context) {
  try {
    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"));
 parameterMapElement(context.evalNodes("/mapper/parameterMap"));
 resultMapElements(context.evalNodes("/mapper/resultMap"));
 sqlElement(context.evalNodes("/mapper/sql"));
 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
 } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
 }
}

在方法内部,将 mapper 标签的属性以及子标签都做了解析操作,下面具体看一下对增删改查标签的解析方法 buildStatementFromContext()的实现:

private void buildStatementFromContext(List list, String requiredDatabaseId) {
  for (XNode context : list) {
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
 try {
      statementParser.parseStatementNode();
 } catch (IncompleteElementException e) {
      configuration.addIncompleteStatement(statementParser);
 }
  }
}

很明显,该方法使用 XMLStatementBuilder 的对象来解析 SQL 语句的标签,来看一下 parseStatementNode()的具体实现:

public void parseStatementNode() {
  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);
 String resultMap = context.getStringAttribute("resultMap");
 String resultType = context.getStringAttribute("resultType");
 String lang = context.getStringAttribute("lang");
 LanguageDriver langDriver = getLanguageDriver(lang);   Class resultTypeClass = resolveClass(resultType);
 String resultSetType = context.getStringAttribute("resultSetType");
 StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
 ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);   String nodeName = context.getNode().getNodeName();
 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:  and  were parsed and removed)
 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))
        ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
 }

  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
 fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
 resultSetTypeEnum, flushCache, useCache, resultOrdered, 
      keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }

这里将标签中的所有属性都解析出来,通过addMappedStatement()方法保存在一个 MappedStatement 中,下面来看一下 addMappedStatement()方法的具体实现:

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) 

通过方法定义可以看出,返回值是一个MappedStatement 对象。所以,可以得出一个结论:每一个 MapperStatement 里面都包含了一个增删改查标签的所有详细信息。方法中还有一下语句:

configuration.addMappedStatement(statement);

说明增删改查标签的所有详细信息最终也是保存在 configuration 中的。

至此,第一个步骤获取 SqlSessionFactory 的所有运行流程都已经全部分析完毕。总结一下,主要工作有两点:

1.配置文件中的信息解析并保存在 Configuration 对象中,

2.返回一个 DefaultSqlSessionFactory 对象。

最后,补上一张时序图,看上去更直观。

关注微信公众号:Javall咖啡屋
每天更新各种技术学习心得体会

你可能感兴趣的:(JAVA,Mybatis,Java,Mybatis)