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; }</pre>

可以看见在方法内部调用了类的内部方法 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<? extends Log> logImpl = (Class<? extends Log>)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<XNode> 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: <selectKey> and <include> 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 对象。

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

image

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

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值