MyBatis源码解读(四)

3.3、MyBatis运行流程

3.3.1、配置解析

ini 复制代码
 InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

通过IO方式打开输入流,获取mybatis-config.xml以及xxxMapper.xml,因为在mybatis-config.xml中已经配置好了mapper文件的路径,所以在以一次的IO中把两者的信息都读到了。

那么在读取完配置文件以后,mybatis该如何处理所读取到的这些配置信息呢?在读取完mybatis-config.xml文件后,所有的配置都将会被封装成Configuration对象。那么此时问题又来了,如何将xml对象转为java对象呢?java提供了xml解析的一整套完整的结局方案。

xml解析在java中有3种方式,而mybatis采用的是第三种方式:

  1. dom
  2. sax
  3. xpath

在读取配置文件的这一行代码肯定会包含解析xml配置文件的步骤,所以我们跟进来看看这个build方法。

ini 复制代码
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

接着点这个build方法。

可以看到他是有解析这个xml的方法。我们点进去

再点进去,我们会看到一个parseConfiguration方法,这个方法用于解析一个个的标签。

随便抽出一行来解析可以看到,这个方法是用于解析mappers标签的方法。

less 复制代码
 mapperElement(root.evalNode("mappers"));

我们继续溯源,点mapperElement方法来剥丝抽茧。

我们在写mappers标签的时候,如果写了packages属性(写dao接口所在的包的路径)的话,就不用再单独写一个个的dao类的路径,因为他会去扫描这个路径所在的所有的类。

ini 复制代码
 if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        }

如果我们不写packeags这个属性的话,就会走到else分支里面。

ini 复制代码
      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);
            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.");
          }

前三行其实是在获取我们写的属性。

ini 复制代码
     String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");

如果我们这样写标签的话

ini 复制代码
 <mapper resource="cn/linstudy/mapper/DeptMapper.xml"></mapper>

那么resource的值就是cn/linstudy/mapper/DeptMapper.xml,而url、mapperClass的值将会全部为空。然后就到了第一个if判断语句。

ini 复制代码
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();
            }
}

我们看到这个if条件语句可以得知,mapper标签里面的属性,只有resource是不可以为空,其他均可以为空。如果resource不为空的话就可以获取到这个属性对应的值。

然后我们就可以开始看parse这个方法,用于解析。

然而负责解析maapper的是第三行。

less 复制代码
 configurationElement(parser.evalNode("/mapper"));

所以我们接着顺藤摸瓜。

我们可以看到这个方法是真正开始读取我们的xxxMapper.xml文件,也就是读取我们写的一些sql语句了。

其中这个方法最重要的是buildStatementFromContext(context.evalNodes("select|insert|update|delete"));这一句,我们点进去buildStatementFromContext方法。

其中statementParser的parseStatementNode是将传过来的参数解析成mapperstatememt,我们来看看parseStatementNode方法。

我们看到这些红框标出来的有没有感到特别的熟悉呢?

没错这些就是在mapper.xml文件中写的每一个标签里面的属性。

我们再返回到parseStatementNode这个方法,我们可以发现前面的步骤都是在为了120行的addMappedStatement方法做数据准备。

所以解析了那么多标签里面的数据,其实最终的·目的都是为了addMappedStatement来创建一个mapperstatement来准备数据的。我们进入这个方法。

我们可以发现一个很经典的设计模式------构造者设计模式,构造者设计模式其实本质上是为了创建对象的,但凡使用了创建者模式创建对象的话,他创建对象的方法都是叫做build。

这里就创建了一个MappedStatement对象,使用的也是创建者模式。

在创建完mapperstatement对象后就存入到configuration对象里面去了。

ini 复制代码
    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);

3.3.2、SqlSessionFactory

了解完配置的解析以后,我们来看看SqlSessionFactory的创建。我们找到我们写过的测试类,有一个build方法可以点进去。

再点这个build方法。

好家伙,还有一个build方法,我们可以看到build方法创建了一个默认的DefaultSqlSessionFactory对象。

3.3.3、openSession

我们看完SqlSessionFactory创建以后,来看看sqlSessionFactory的openSession方法。

然后我们发现这是一个接口方法。

我们找第一个实现,因为SqlSessionFactory是由DefaultSqlSessionFactory创建的,所以我们找DefaultSqlSessionFactory实现的方法即可。

点进去后是一个openSessionFromDataSource方法,我们可以发现一个特别有意思的现象,我们来看传入的参数是一个ExecutorType,也就是说虽然说是openSession,但是实际上真正干活的是Executor

然后我们继续点进去这个方法,接着跟,这个就是创建sqlSession的核心。

这里有一行很关键的代码。

arduino 复制代码
return new DefaultSqlSession(configuration, executor, autoCommit);

我们可以发现,创建一个DefaultSqlSession需要三个参数:

  1. configuration:提供各种配置信息
  2. executor:执行数据库的各种增删改查
  3. autoCommit:事务是自动提交
相关推荐
丁总学Java4 分钟前
如何使用 maxwell 同步到 redis?
数据库·redis·缓存
爱吃南瓜的北瓜12 分钟前
Redis的Key的过期策略是怎样实现的?
数据库·redis·bootstrap
一心只为学26 分钟前
Oracle密码过期问题,设置永不过期
数据库·oracle
小光学长35 分钟前
基于vue框架的宠物销售管理系统3m9h3(程序+源码+数据库+调试部署+开发环境)系统界面在最后面。
数据库
wn53142 分钟前
【Go - 类型断言】
服务器·开发语言·后端·golang
小菜yh1 小时前
关于Redis
java·数据库·spring boot·redis·spring·缓存
希冀1231 小时前
【操作系统】1.2操作系统的发展与分类
后端
Microsoft Word1 小时前
数据库系统原理(第一章 数据库概述)
数据库·oracle
华为云开源1 小时前
openGemini 社区人才培养计划:助力成长,培养新一代云原生数据库人才
数据库·云原生·开源
GoppViper2 小时前
golang学习笔记29——golang 中如何将 GitHub 最新提交的版本设置为 v1.0.0
笔记·git·后端·学习·golang·github·源代码管理