MyBatis工作原理源码分析(一)——SqlSessionFactory的初始化

一、引言

在Spring项目配置中,配置Mybatis时会有如下配置,这样,Spring容器会自动将SqlSessionFactory注入到容器中。

<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
	<!-- 指定mybatis全局配置文件的位置 -->
	<property name="configLocation" value="classpath:mybatis-config.xml"></property>
	<property name="dataSource" ref="PooledDataSource"></property>
	<property name="mapperLocations" value="classpath:mapper/*.xml"></property>
</bean>

但手动使用Mybatis时,通常有如下四个步骤:

获取SqlSessionFactory对象;
获取sqlSession对象;
获取接口的代理对象(MapperProxy);
执行增删改查sql方法;

对应的代码如下:

@Test
public void test() throws IOException{
 	   String resource = "mybatis-config.xml";
       InputStream inputStream = Resources.getResourceAsStream(resource);
       
       // 1、获取sqlSessionFactory对象
       SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
       // 2、获取sqlSession对象
       SqlSession openSession = sqlSessionFactory.openSession();
       try {
           // 3、获取接口的实现类对象,会为接口自动的创建一个代理对象,代理对象去执行增删改查方法
           DepartmentMapper mapper = openSession.getMapper(DepartmentMapper.class);
           // 4、执行增删改查方法
           Department dept = mapper.getDeptById(1);
           System.out.println(dept);
       } finally {
           openSession.close();
       }
    }

二、源码分析

下面来分析第一步,SqlSessionFactory 是如何创建的?

1、首先,SqlSessionFactoryBuilder会去读取mybatis的配置文件,然后build一个DefaultSqlSessionFactory。源码如下:

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {

    try {
      //创建XMLConfigBuilder解析器,用于解析mybatis的配置文件
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      
      //第一步:调用解析器的parse()方法进行mybatis配置文件的解析,返回一个Configuration!!!!
      //第二步:再将Configuration参数用build()方法创建一个DefaultSqlSessionFactory对象
      return build(parser.parse());
      
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
      }
    }
  }
 
//用解析完成后返回的Configuration,创建一个DefaultSqlSessionFactory对象
public SqlSessionFactory build(Configuration config) {
   return new DefaultSqlSessionFactory(config);
 }

2、XMLConfigBuilder解析器的parse()解析方法,返回Configuration

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    
    //parser.evalNode(“/configuration”) 用XPathParser类的evalNode方法,获取配置文件的根节点<configuration>
    //返回的是XNode对象,XNode对象保存了解析后的对应节点下的所有信息。
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

3、解析每一个标签,并把详细信息保存在Configuration对象中

private void parseConfiguration(XNode root) {
    try {

      // 解析properties节点并设置到Configuration
      propertiesElement(root.evalNode("properties"));
      
      //获取了<configuration>下的<settings>节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      
      //解析typeAliases节点并设置到Configuration
      typeAliasesElement(root.evalNode("typeAliases"));
      //解析plugin节点并设置到Configuration
      pluginElement(root.evalNode("plugins"));
      //解析objectFactory节点并设置到Configuration
      objectFactoryElement(root.evalNode("objectFactory"));
      //解析objectWrapperFactory节点并设置到Configuration 
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      //解析settings节点并设置到Configuration  
      settingsElement(settings);
      //解析environments节点并设置到Configuration  即Environments对象
      environmentsElement(root.evalNode("environments"));
      //解析databaseIdProvider节点并设置到Configuration
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //解析typeHandlers节点并设置到Configuration    
      typeHandlerElement(root.evalNode("typeHandlers"));
      //解析mappers节点并设置到Configuration
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

4、解析配置文件中节点(如下)下的mappers节点并设置到Configuration

<mappers>
	<mapper resource="EmployeeMapper.xml" />
</mappers>
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 {
       	  //获取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);
            //用XMLMapperBuilder 解析器,解析mapper文件
            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.");
          }
        }
      }
    }
  }

//XMLMapperBuilder解析器中的parse()方法用来解析mapper文件
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      //解析mapper.xml文件中的内容
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

//解析mapper.xml文件中的内容,主要是增删改查标签的解析
private void configurationElement(XNode context) {
    try {
      //mapper文件中的命名空间属性
      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"));
      //可重用的sql
      sqlElement(context.evalNodes("/mapper/sql"));
      
      //解析mapper.xml文件中的增删改查标签!!!!!!
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

  //解析mapper.xml文件中的增删改查标签
  private void buildStatementFromContext(List<XNode> list) {
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

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

public void parseStatementNode() {
	//拿到每一个增删改查的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);
   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))
         ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
   }

	//将标签中的所有信息拿出来,调用addMappedStatement()方法,该方法会返回一个MappedStatement对象。
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

此处可以知道,一个MappedStatement对象,就代表一个增删改查标签的详细信息。然后将MappedStatement存放到Configuration中

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.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 statement = statementBuilder.build();
  //将MappedStatement存放到Configuration中
  configuration.addMappedStatement(statement);
  return statement;
}

三、小结

上面就是根据配置文件创建SQLSessionFactory的详细过程,下面是对应的时序图:
在这里插入图片描述

总结:
(1)、一个MappedStatement代表一个增删改查的详细信息
(2)、把Mybatis配置文件的信息解析并保存在Configuration对象中,返回包含了Configuration的DefaultSqlSession对象;
(3)、Configuration封装了所有配置文件的详细信息,如下图所示
在这里插入图片描述

在这里插入图片描述

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

止步前行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值