Mybatis源码分析与技术原理

Posted 愉悦滴帮主)

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis源码分析与技术原理相关的知识,希望对你有一定的参考价值。

前言

Mybatis框架属于ORM框架,全称(Object Relational Mapping)。用于实现面向对象main车工语言里不同类型系统之间的数据之间的转换。我们在开发中Mybatis框架通常作为Java链接mysql的工具,那么mytais底层是如何查询数据库的?又是如何将数据库对应的类型(varchar)转换为对应的Java基本类型的?下面我们来介绍mybatis的底层原理。

一、Mybatis是什么?

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。


二、传统 JDBC 的弊端:

1、jdbc 底层没有用连接池、操作数据库需要频繁的创建和关联链接。消耗很大的资源

2、写原生的 jdbc 代码在 java 中,一旦我们要修改 sql 的话,java 需要整体编译,不利于系统维护

3、使用 PreparedStatement 预编译的话对变量进行设置 123 数字,这样的序号不利于维护

4、返回 result 结果集也需要硬编码。

二、传统Mybatis使用步骤

1.引入包

1.如果不是Maven项目,要使用 MyBatis, 只需将 mybatis-x.x.x.jar 文件置于类路径(classpath)中即可。

2.如果使用 Maven 来构建项目,则需将下面的依赖代码置于 pom.xml 文件中:

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>


2.从 XML 中构建 SqlSessionFactory

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的 Configuration 实例来构建出 SqlSessionFactory 实例。

从 XML 文件中构建 SqlSessionFactory 的实例非常简单,建议使用类路径下的资源文件进行配置。 但也可以使用任意的输入流(InputStream)实例,比如用文件路径字符串或 file:// URL 构造的输入流。MyBatis 包含一个名叫 Resources 的工具类,它包含一些实用方法,使得从类路径或其它位置加载资源文件更加容易。

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

XML 配置文件中包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器(TransactionManager)。后面会再探讨 XML 配置文件的详细内容,这里先给出一个简单的示例:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC"/>
      <dataSource type="POOLED">
        <property name="driver" value="${driver}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="${username}"/>
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.mybatis.example.BlogMapper">
    <select id="selectBlog" resultType="org.apache.ibatis.demo.Blog">
        SELECT * FROM Blog WHERE id = #{id}
    </select>
</mapper>

 当然,还有很多可以在 XML 文件中配置的选项,上面的示例仅罗列了最关键的部分。 注意 XML 头部的声明,它用来验证 XML 文档的正确性。environment 元素体中包含了事务管理和连接池的配置。mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。

3.从 SqlSessionFactory 中获取 SqlSession

既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:

try (SqlSession session = sqlSessionFactory.openSession()) {
  Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
}

4.最后的测试类

public class MybatisMain {
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        Blog blog =  session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
        System.out.println(blog);

    }
}

三、Mybatis源码编译与下载

1.首先从github上拉去Mybatis源码项目。

https://github.com/mybatis/mybatis-3

https://github.com/mybatis/parent(依赖)

 在目标文件夹下,通过git clone的方式拉取代码。

2.将Mybatis源码项目导入idea开发工具。注意导入的是maven项目。

3.解决parent依赖问题

在构建的过程中会出现找不到pom.xml中依赖的父模块mybatis-parent的问题。我们需要将paren工程克隆到本地目录中:git clone https://github.com/mybatis/parent.git ,然后先进入parent工程下进行mvn clean install 将parent工程依赖的包下载下来、并保证parent工程编译通过,这步不会出现问题,在编译的输出信息中我们会看到parent工程的版本号,如图所示:

 

pom.xml文件parent依赖的version标签处,如下文。 
接下来修改mybatis工程的pom.xml文件中标识parent依赖的地方:


  <parent>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-parent</artifactId>
    <version>28-SNAPSHOT</version>
    <relativePath>../parent/pom.xml</relativePath>
  </parent>

 

告诉我们部分插件没有指定的相应的版本号,出于工程的稳定性考虑需要对使用的插件指定其版本号,并给出了合适的版本号,如图红色方框中的文字。我们只要在mybatisg工程的pom.xml文件中找到相对应的插件处添加<version>$NUM</version> 标签即可, $NUM代表具体的版本号。到这我们再执行mvn clean install 指令就可以将mybatis工程构建成功了。

 


四、Mybatis连接数据库流程

 

Mybatis连接MySQL数据库,首先Mybatis首先会获取数据源,对数据源执行SQL语句,在执行SQL语句的时候会先获取到连接,创建句柄防止sql注入,访问数据库,最后返回结果集。

1.Mybatis是如何获取数据源

   public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        Blog blog =  session.selectOne("org.apache.ibatis.demo.BlogMapper.selectBlog", 101);
         System.out.println(blog);
    }

我们的数据源是放在mybatis-config.xml这个配置文件中的也就是说谁去解析这个配置文件谁就获得了数据源。我们通过debug上述代码的形式去分析。

debug调用链:

  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.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

此时我们已经得到了datesource数据源

2.Mybatis是如何执行sql语句

Sql语句主要放在BlogMapper.xml这个配置文件中,也就是说谁去解析BlogMapper.xml文件,谁就获得执行sql的权利。

debug调用链:

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

​

 此时通过buildStatementFromContext(context.evalNodes("select|insert|update|delete"));这行代码已经得到了BlogMapper.xml配置文件中的信息。后面就看如何执行sql。


面试题:Mybatis 加载 Mapper配置的方式有几种?

Mybatis官网中说到映射器(mappers)的配置方式。如下:

1.使用相对于类路径的资源引用 resource

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>

2.使用完全限定资源定位符 url

<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>

3.使用映射器接口实现类的完全限定类名 class

<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>

4.将包内的映射器接口实现全部注册为映射器 package

<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers

这四种加载mapper的方式中哪种优先级最高呢?

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配置的情况
                String resource = child.getStringAttribute("resource");
                String url = child.getStringAttribute("url");
                String mapperClass = child.getStringAttribute("class");
                if (resource != null && url == null && mapperClass == null) {
                    // resource属性不为空
                    ErrorContext.instance().resource(resource);
                    InputStream inputStream = Resources.getResourceAsStream(resource);
                    XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
                    mapperParser.parse();
                } else if (resource == null && url != null && mapperClass == null) {
                    // url属性不为空
                    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属性不为空
                    Class<?> mapperInterface = Resources.classForName(mapperClass);
                    configuration.addMapper(mapperInterface);
                // resource、url和class只能存在一个
                } else {
                    throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                }
            }
         }
      }
    }

 


3.Mybatis是如何执行数据库

通过上述步骤我们已经可以得到具体的sql语句,只要交给Mybatis的执行器去执行。

Mybatis的执行器有几种?

SimpleExecutor:每执行一次update或select,就开启一个Statement对象,用完立刻关闭Statement对象。

ReuseExecutor:执行update或select,以sql作为key查找Statement对象,存在就使用,不存在就创建,用完后,不关闭Statement对象,而是放置于Map内,供下一次使用。简言之,就是重复使用Statement对象。

BatchExecutor:执行update(没有select,JDBC批处理不支持select),将所有sql都添加到批处理中(addBatch()),等待统一执行(executeBatch()),它缓存了多个Statement对象,每个Statement对象都是addBatch()完毕后,等待逐一执行executeBatch()批处理。与JDBC批处理相同。

作用范围:Executor的这些特点,都严格限制在SqlSession生命周期范围内。

Mybatis中默认使用哪一种Executor执行器?

答:SimpleExecutor执行器。

Mybatis中如何指定使用哪一种Executor执行器?

答:在Mybatis配置文件中,可以指定默认的ExecutorType执行器类型,也可以手动给DefaultSqlSessionFactory的创建SqlSession的方法传递ExecutorType类型参数。


总结:

 

以上是关于Mybatis源码分析与技术原理的主要内容,如果未能解决你的问题,请参考以下文章

MyBatis源码分析插件实现原理

Mybatis Mapper接口是如何找到实现类的-源码分析

MyBatis 源码分析 - 缓存原理

MyBatis 源码分析 - 缓存原理

MyBatis核心源码深度剖析工作机制和实现原理

从源码角度分析 MyBatis 工作原理