IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 开发测试 -> Mybatis源码解析(一)创建会话SqlSession -> 正文阅读

[开发测试]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对象并返回

附图一张:

在这里插入图片描述

  开发测试 最新文章
pytest系列——allure之生成测试报告(Wind
某大厂软件测试岗一面笔试题+二面问答题面试
iperf 学习笔记
关于Python中使用selenium八大定位方法
【软件测试】为什么提升不了?8年测试总结再
软件测试复习
PHP笔记-Smarty模板引擎的使用
C++Test使用入门
【Java】单元测试
Net core 3.x 获取客户端地址
上一篇文章      下一篇文章      查看所有文章
加:2021-09-08 11:02:20  更:2021-09-08 11:03:25 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年5日历 -2024/5/21 2:01:53-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码