Mybatis源码解析(一)创建会话SqlSession

为了避免其他类导致的干扰,该项目只导入了mybatis依赖、mysql驱动包和junit测试依赖,具体如下:

<dependencies>
        <!--mybatis核心包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.3</version>
        </dependency>
        <!--mysql驱动包-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!-- 单元测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

首先我们来创建一个测试类:

	@Test
    public void testFindAll() throws IOException {
        //加载主配置文件,目的是为了构建SqlSessionFactory对象
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

        //创建SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

        //通过SqlSessionFactory工厂对象创建SqlSesssion对象
        SqlSession sqlSession = factory.openSession();

        //通过Session创建UserDao接口代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        
        List<User> userList = mapper.findAll();
        for (User user : userList) {
            System.out.println(user);
        }

    }

结果我就不贴了

本篇文章需要搞懂的是mybatis的底层是如何创建SqlSession会话的?以及在创建会话的时候做了什么?

接下来我们跟着源码走就行了,下面我来一一讲解,首先我们看下面代码:

//加载主配置文件,目的是为了构建SqlSessionFactory对象
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

//创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

//通过SqlSessionFactory工厂对象创建SqlSesssion对象
SqlSession sqlSession = factory.openSession();

第一句代码是把mybatis的配置文件转化成流的方式,我们重点看第二句代码和第三句代码,先看第二句,我们跟着源码进去

在SqlSessionFactoryBuilder类中

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

该方法调用另一个build()方法

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      //创建一个xml配置文件解析器
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      //parse.parse()执行解析
      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.
      }
    }
  }

我们再进去parser.parse()方法看看

在XMLConfigBuilder 类中

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

我们的mybatis配置文件的根标签就是标签,现在再进去parseConfiguration()方法

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"));
      //解析xml文件
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

我们根据mybatis官网知道mappers标签可以配置的内容如下:
在这里插入图片描述

我们不关心其他标签的解析,我们看看它是怎么解析标签的,标签里面配置的是我们xml文件的路径或者mapper接口的路径,进去mapperElement()

  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();
            //使用完全限定资源定位符(URL)
          } 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>
   <mapper resource="mapper/UserMapper.xml"></mapper>
</mappers>

所有我们进入第一个if语句

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());
         //解析xxxMapper.xml文件
         mapperParser.parse();
} 

现在进入mapperParser.parse()方法

在XMLMapperBuilder类中

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      //解析根标签<mapper>
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

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

我们现在知道这个方法是解析mapper.xml文件的,我们看它解析根标签,进入configurationElement()方法

  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"));
      //解析sql命令标签,创建MappedStatement
      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);
    }
  }

现在明确了上面的方法是把一个个的sql标签封装成Statement,它里面的细节我们不用过于花心去追究,我们只知道解析的结果就知道了

现在源码探索到这了,我们就知道我们已经解析了mybatis配置文件和mapper.xml配置文件,并且把里面的sql语句封装成了MappedStatement

我们回到最初的方法,在SqlSessionFactoryBuilder类中

  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()方法即build(parser.parse())

我们再进入这个build()方法

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

该方法最终返回了一个DefaultSqlSessionFactory对象

再回到调用的语句

//加载主配置文件,目的是为了构建SqlSessionFactory对象
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");

//创建SqlSessionFactory对象
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);

//通过SqlSessionFactory工厂对象创建SqlSesssion对象
SqlSession sqlSession = factory.openSession();

至此,我们执行完了第二句代码,mybatis配置文件解析完了,mapeer.xml文件也解析和封装完了

现在我们来看第三句代码,我们继续跟踪源码

在DefaultSqlSessionFactory类中

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

configuration.getDefaultExecutorType()表示使用默认的执行器(SimpleExecutor),再进入openSessionFromDataSource()方法

  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      final Environment environment = configuration.getEnvironment();
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      final Executor executor = configuration.newExecutor(tx, execType);
      //返回一个DefaultSqlSession对象
      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();
    }
  }

该方法返回一个DefaultSqlSession对象,并且传入了全局配置configuration,执行器executor和是否自动提交,到此,我们得到了一个DefaultSqlSession对象并返回

附图一张:

在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

华达州

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

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

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

打赏作者

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

抵扣说明:

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

余额充值