橘子学Mybatis05之源码中处理sql之前的所有工作

一、概述

之前我们说了mybatis中的动态代理模式,我们知道了在mybatis中dao接口的实现类是通过动态代理(jdk)创建的,创建出来的实现类里面通过调用sqlsession去执行jdbc操作,这就和我们自己用sqlsession操作后续的流程是一样的了,就是下面这个流程。
在这里插入图片描述
也就是sqlsession就顺着那个excutor的路子下去了,这就把这个动态代理的类和我们之前说的那些组件关联起来了。
OK,我们此时再来看一下那段核心代码:

// 读取配置文件转化为流
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
//这⾥不调⽤SqlSession的api,⽽是获得了接⼝对象,调⽤接⼝中的⽅法,底层再用sqlsession的方法。使用JDK动态代理产生代理对象
IUserDao userDao = sqlSession.getMapper(IUserDao.class);

至此为止,我们解决了那段动态代理的代码了,剩下的就是上面那三行了,也就是这段:

// 读取配置文件转化为流
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();

我们来看这段代码,我们看到他做的就是通过io流读取sqlMapConfig.xml这个核心配置文件,我们知道这里配置了所有的核心配置,并且我们在核心配置文件里面还注册了mapper.xml的路径,也就是这个。

 <mappers>
	<mapper resource="UserMapper.xml"/>
</mappers>

我们在核心配置里面注册了mapper.xml的路径,所以他读取核心配置也就能读取到mapper.xml文件,所以顺藤摸瓜就能把所有的配置文件一起读取到。
OK我们下面就来看看具体是怎么个读取流程。

二、读取流程

// 读取流程
// 获取文件,并且读取转化为输入流
InputStream inputStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 输入流作为参数,我们就跟进去这个build方法
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建流程
SqlSession sqlSession = factory.openSession();

首先明确一点,那种什么什么Builder对象都是建造者模式的体现,而他调用的build方法就是真正执行的地方。

开始看源码

我们点进去build方法看一下。来到一个重载方法:

public SqlSessionFactory build(InputStream inputStream) {
  return build(inputStream, null, null);
}

跟进这个重载方法:

/**
  * @Author levi
  * @Param  * @param inputStream  参数是这个配置文件的输入流
  * @param environment   这里传参进来是Null,没设置东西
  * @param properties     这里传参进来是Null,没设置东西
  * @return org.apache.ibatis.session.SqlSessionFactory
  **/
 public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
   try {
     // 创建XMLConfigBuilder 看名字像是一个xml配置文件的建造者,传进去参数是文件的流 其余那两个我们前面看到是Null,所以不做分析  入口1 我们就来分析一下这个入口做了什么
     XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
     // 入口2
     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.
     }
   }
 }

1、入口1:创建XMLConfigBuilder

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
 super(new Configuration());
  ErrorContext.instance().resource("SQL Mapper Configuration");
  this.configuration.setVariables(props);
  // 是否解析过了,解析过就是false
  this.parsed = false;
  this.environment = environment;
  this.parser = parser;
}
我们看到他就是创建出一个Configuration对象,以及给这个对象设置了一些值,并且把一些变量给了初始值。

基于此我们看到入口1就是创建了一个前期准备的类。所以我们知道重点就落在了入口2这里,我们点进去看看入口2、

2、入口2:正式开始工作

入口2的代码有两步:
1、parser.parse()
2、build(parser.parse())
我们就分两步来看看这两步到底干啥了。

2.1、parser.parse()

我们点进去看看这个方法。

public Configuration parse() {
  // 解析过了就是false,之前还没解析过呢,上面我们设置为false,下面开始解析
  if (parsed) {
  	// 解析过了抛出只能解析一次的异常
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  // 走过判断,设置为true,也就是解析过了,下次再解析,就抛出上面的异常
  parsed = true;
  // 这是xpath的语法,他解析xml,从头节点configuration开始解析,所以这里就是解析xml,
  parseConfiguration(parser.evalNode("/configuration"));
  // 经过下面的封装,最后返回这个分装好的configuration对象。
  return configuration;
}

于是parseConfiguration()方法·就成了最后的落脚点。
我们点进去看看。

2.1,1、解析核心配置
private void parseConfiguration(XNode root) {
   try {
     // issue #117 read properties first
     propertiesElement(root.evalNode("properties"));
     Properties settings = settingsAsProperties(root.evalNode("settings"));
     loadCustomVfs(settings);
     loadCustomLogImpl(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"));
     // 解析mapper文件,这里注意,他并且解析了mapper.xml文件,因为我们在配置文件中,有这个配置
     /**
		<mappers>
            <!--<package name="com.lwq.dao"/>-->
          <mapper resource="UserMapper.xml"/>
        </mappers>
        我们这里读取这个节点其实就能找到mapper.xml文件了。也就能去解析这个文件了。
	*/
     mapperElement(root.evalNode("mappers"));
   } catch (Exception e) {
     throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
   }
 }

我们看到上面使用xpath解析了很多xml的标签,这些标签的属性都是在我们的sqlMapconfig.xml文件中配置好的。所以这里其实就是一个个的标签一点点的解析出来。我们随便点开一个看看,就以environmentsElement(root.evalNode(“environments”));为例点进去看看。

// 解析xml文件,读取environments标签
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中
        configuration.setEnvironment(environmentBuilder.build());
        break;
      }
    }
  }
}
我们看到最后读取完了,最后封装到了configuration里面,所以所有的属性最后都封装到了这里configuration这个类里面,这里是汇总一切的地方,之前我们说过了。

OK我们这一步就得到了configuration对象结束了。

2.1.2、解析mapper.xml文件

我们上面也看到了他同时在解析sqlMapConfig.xml的时候也还处理了mapper.xml文件,也就是这段代码:

 // 解析mapper文件,这里注意,他并且解析了mapper.xml文件,我们点进去看看
  mapperElement(root.evalNode("mappers"));
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        // 如果你在配置文件里面是以package配置的就走这里
        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);
            try(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);
            // 这里也解析了xml,其实这里就是解析mapper.xml的入口,上面那个分支也是,我就拿这个示例了,点进去看看。
            try(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.");
          }
        }
      }
    }
  }
public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
     // 从根标签开始解析
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
 private void configurationElement(XNode context) {
    try {
      // 下面就是解析一系列的标签
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        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"));
      // 这里又是一个Builfd的入口,我们跟进去看看
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

最后点进去方法来到这里

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 ,也就是最后mapper.xml文件会被
	// 解析,把内部属性全部封装到对象MappedStatement 中,这样他就存在于jvm中,以后用的地方就能直接拿来用。
    MappedStatement statement = statementBuilder.build();
    // 并且MappedStatement 还封装在了configuration对象里面,至此所有的文件都被解析完成了。
    configuration.addMappedStatement(statement);
    return statement;
  }

这是第一个大路子,接下来我们走入第二个路子。

2.2、build(parser.parse())

点进去来到这里:

public SqlSessionFactory build(Configuration config) {
   //. 我们看到这里以前面执行解析文件封装出来的Configuration 为参数,这里啥都有,创建了一个DefaultSqlSessionFactory对象返回了。
   return new DefaultSqlSessionFactory(config);
 }

至此,我们得到了一个拥有配置文件里面一切内容的Configuration 对象,和一个DefaultSqlSessionFactory,而DefaultSqlSessionFactory这个一看就是一个工厂类,看名字也知道是创建SqlSession的工厂,于是来到了下一步。SqlSession sqlSession = factory.openSession();

三、创建流程

关于创建流程其实就是这段代码:

SqlSession sqlSession = factory.openSession();

factory就是我们之前读取流程的DefaultSqlSessionFactory,于是我们此时很清楚,我们点进去openSession这个方法即可。而且我们上面看到他创建的是DefaultSqlSessionFactory,所以我们跟进去DefaultSqlSessionFactory这个选择。
在这里插入图片描述

@Override
 public SqlSession openSession() {
   /**
    * 这里面三个参数,configuration就是前面初始化的时候封装的配置
    * 第二个level:是事务的隔离级别,这里我们没设置,就是null,使用默认的RR
    * 第三个autoCommit:设置自动事务提交,这里设置为false,不自动提交
    */
   return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
 }

再点进去:

/**
   *  进⼊penSessionFromDataSource。
   *  ExecutorType 为Executor的类型,TransactionIsolationLevel为事务隔离级别, autoCommit是否开启事务
   *  penSession的多个重载⽅法可以指定获得的SeqSession的Executor类型和事务的处理
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      //.获取配置文件里面的环境environment 标签,这里面有数据库连接信息
      final Environment environment = configuration.getEnvironment();
      // 事务相关
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 根据configuration里面的类型创建executor,我们看到了,sqlsession其实也是最后使用excutor	 来往下调用的,
      // 这个我们前面结构分析说过了。Executor等那几个组件是通过configuration创建出来的。
      // 所以他这里就创建出来了。
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建返回一个DefaultSqlSession,里面封装了创建好的configuration, executor, autoCommit
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }
public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    // 创建sqlsession的时候就把Executor 也加进去了
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

我们看到最后创建了一个创建返回一个DefaultSqlSession,这里面此时封装了一些事务属性以及configuration这个对象,里面包含了一切我们前面解析xml时候的东西,至此创建出来了sqlsession,并且里面有配置文件的所有东西,并且里面还包含了excutor对象,excutor里面还有各种handler,后面就是我们前面说的过程了,就是这里就前后接上了。
至此我们经过读取和创建两个流程,解析完了执行sql之前的所有准备工作。此时就通过解析xml拿到了所有的执行的内容包括sql和参数封装到了configuration和MappedStatement ,并且通过configuration创建出来了Executor 执行器,一起封装在了DefaultSqlSession(sqlSession的子类)中,此时用户角度的sqlsession就拥有了执行的能力。而他此时还是一个接口,下面就开始通过我们的动态代理开始创建实现类,然后就可以开始执行下面的代码了。但是执行的时候,我们是要执行真正的sql的,其实这个真正的sql在解析的时候就解析了sql和参数封装好了,后面拿到直接传入JDBC进行执行。

四、思辨

我们看了一下上面的代码,发现很多地方都是工厂模式和建造者模式的使用,这些设计模式的使用使得代码的结构很优秀,解耦还是扩展都很好。
再有一个就是我们看到Mybatis使用了xpath解析了xml文件,其中各种api的使用我们以后再工作的时候如果要解析xml,可以来抄一抄这里配置文件的解析操作,这也是学源码的一个作用吧。
至于xpath,我看到糊涂工具包里面也封装了一波,下面是地址,向所有致力于开源的工作者们致以敬意。
https://hutool.cn/docs/#/core/%E5%B7%A5%E5%85%B7%E7%B1%BB/XML%E5%B7%A5%E5%85%B7-XmlUtil

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值