面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他(二)

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
简介: MyBatis 是第一个支持自定义 SQL、存储过程和高级映射的类持久框架。MyBatis 消除了大部分 JDBC 的样板代码、手动设置参数以及检索结果。MyBatis 能够支持简单的 XML 和注解配置规则。使 Map 接口和 POJO 类映射到数据库字段和记录。

SqlSessionFactory 的执行流程

下面来对 SqlSessionFactory 的执行流程来做一个分析

首先第一步是 SqlSessionFactory 的创建

SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

从这行代码入手,首先创建了一个 SqlSessionFactoryBuilder 工厂,这是一个建造者模式的设计思想,由 builder 建造者来创建 SqlSessionFactory 工厂

然后调用 SqlSessionFactoryBuilder 中的 build 方法传递一个InputStream 输入流,Inputstream 输入流中就是你传过来的配置文件 mybatis-config.xml,SqlSessionFactoryBuilder 根据传入的 InputStream 输入流和environmentproperties属性创建一个XMLConfigBuilder对象。SqlSessionFactoryBuilder 对象调用XMLConfigBuilder 的parse()方法,流程如下。

微信图片_20220412201236.jpg

XMLConfigBuilder 会解析/configuration标签,configuration 是 MyBatis 中最重要的一个标签,下面流程会介绍 Configuration 标签。

MyBatis 默认使用 XPath 来解析标签,关于 XPath 的使用,参见 https://www.w3school.com.cn/xpath/index.asp

parseConfiguration 方法中,会对各个在 /configuration 中的标签进行解析

微信图片_20220412201241.jpg

重要配置

说一下这些标签都是什么意思吧

  • properties,外部属性,这些属性都是可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来传递。
<properties>
    <property name="driver" value="com.mysql.jdbc.Driver" />
    <property name="url" value="jdbc:mysql://localhost:3306/test" />
    <property name="username" value="root" />
    <property name="password" value="root" />
</properties>

一般用来给 environment 标签中的 dataSource 赋值

<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>

还可以通过外部属性进行配置,但是我们这篇文章以原理为主,不会介绍太多应用层面的操作。

  • settings ,MyBatis 中极其重要的配置,它们会改变 MyBatis 的运行时行为。

settings 中配置有很多,具体可以参考 https://mybatis.org/mybatis-3/zh/configuration.html#settings 详细了解。这里介绍几个平常使用过程中比较重要的配置

微信图片_20220412201246.jpg

<settings>
  <setting name="cacheEnabled" value="true"/>
  <setting name="lazyLoadingEnabled" value="true"/>
</settings>
  • typeAliases,类型别名,类型别名是为 Java 类型设置的一个名字。它只和 XML 配置有关。
<typeAliases>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。

  • typeHandlers,类型处理器,无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

org.apache.ibatis.type 包下有很多已经实现好的 TypeHandler,可以参考如下

微信图片_20220412201252.jpg

你可以重写类型处理器或创建你自己的类型处理器来处理不支持的或非标准的类型。

具体做法为:实现 org.apache.ibatis.type.TypeHandler 接口, 或继承一个很方便的类 org.apache.ibatis.type.BaseTypeHandler, 然后可以选择性地将它映射到一个 JDBC 类型。

  • objectFactory,对象工厂,MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。
public class ExampleObjectFactory extends DefaultObjectFactory {
  public Object create(Class type) {
    return super.create(type);
  }
  public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
    return super.create(type, constructorArgTypes, constructorArgs);
  }
  public void setProperties(Properties properties) {
    super.setProperties(properties);
  }
  public <T> boolean isCollection(Class<T> type) {
    return Collection.class.isAssignableFrom(type);
  }
}

然后需要在 XML 中配置此对象工厂

<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>
  • plugins,插件开发,插件开发是 MyBatis 设计人员给开发人员留给自行开发的接口,MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。MyBatis 允许使用插件来拦截的方法调用包括:Executor、ParameterHandler、ResultSetHandler、StatementHandler 接口,这几个接口也是 MyBatis 中非常重要的接口,我们下面会详细介绍这几个接口。
  • environments,MyBatis 环境配置,MyBatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中。例如,开发、测试和生产环境需要有不同的配置;或者想在具有相同 Schema 的多个生产数据库中 使用相同的 SQL 映射。
    这里注意一点,虽然 environments 可以指定多个环境,但是 SqlSessionFactory 只能有一个,为了指定创建哪种环境,只要将它作为可选的参数传递给 SqlSessionFactoryBuilder 即可。
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment);
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(reader, environment, properties);
  • 环境配置如下
<environments default="development">
  <environment id="development">
    <transactionManager type="JDBC">
      <property name="..." value="..."/>
    </transactionManager>
    <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>
  • databaseIdProvider ,数据库厂商标示,MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。
<databaseIdProvider type="DB_VENDOR">
  <property name="SQL Server" value="sqlserver"/>
  <property name="DB2" value="db2"/>
  <property name="Oracle" value="oracle" />
</databaseIdProvider>
  • mappers,映射器,这是告诉 MyBatis 去哪里找到这些 SQL 语句,mappers 映射配置有四种方式
<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>

上面的一个个属性都对应着一个解析方法,都是使用 XPath 把标签进行解析,解析完成后返回一个 DefaultSqlSessionFactory 对象,它是 SqlSessionFactory 的默认实现类。这就是 SqlSessionFactoryBuilder 的初始化流程,通过流程我们可以看到,初始化流程就是对一个个 /configuration 标签下子标签的解析过程。

SqlSession

在 MyBatis 初始化流程结束,也就是 SqlSessionFactoryBuilder -> SqlSessionFactory 的获取流程后,我们就可以通过 SqlSessionFactory 对象得到 SqlSession 然后执行 SQL 语句了。具体来看一下这个过程

微信图片_20220412201303.jpg

在 SqlSessionFactory.openSession 过程中我们可以看到,会调用到 DefaultSqlSessionFactory 中的 openSessionFromDataSource 方法,这个方法主要创建了两个与我们分析执行流程重要的对象,一个是 Executor 执行器对象,一个是 SqlSession 对象。执行器我们下面会说,现在来说一下 SqlSession 对象

SqlSession 对象是 MyBatis 中最重要的一个对象,这个接口能够让你执行命令,获取映射,管理事务。SqlSession 中定义了一系列模版方法,让你能够执行简单的 CRUD 操作,也可以通过 getMapper 获取 Mapper 层,执行自定义 SQL 语句,因为 SqlSession 在执行 SQL 语句之前是需要先开启一个会话,涉及到事务操作,所以还会有 commitrollbackclose 等方法。这也是模版设计模式的一种应用。

MapperProxy

MapperProxy 是 Mapper 映射 SQL 语句的关键对象,我们写的 Dao 层或者 Mapper 层都是通过 MapperProxy 来和对应的 SQL 语句进行绑定的。下面我们就来解释一下绑定过程

微信图片_20220412201307.jpg

这就是 MyBatis 的核心绑定流程,我们可以看到 SqlSession 首先调用 getMapper 方法,我们刚才说到 SqlSession 是大哥级别的人物,只定义标准(有一句话是怎么说的来着,一流的企业做标准,二流的企业做品牌,三流的企业做产品)。

SqlSession 不愿意做的事情交给 Configuration 这个手下去做,但是 Configuration 也是有小弟的,它不愿意做的事情直接甩给小弟去做,这个小弟是谁呢?它就是 MapperRegistry,马上就到核心部分了。MapperRegistry 相当于项目经理,项目经理只从大面上把握项目进度,不需要知道手下的小弟是如何工作的,把任务完成了就好。最终真正干活的还是 MapperProxyFactory。看到这段代码 Proxy.newProxyInstance ,你是不是有一种恍然大悟的感觉,如果你没有的话,建议查阅一下动态代理的文章,这里推荐一篇 (https://www.jianshu.com/p/95970b089360)

也就是说,MyBatis 中 Mapper 和 SQL 语句的绑定正是通过动态代理来完成的。

通过动态代理,我们就可以方便的在 Dao 层或者 Mapper 层定义接口,实现自定义的增删改查操作了。那么具体的执行过程是怎么样呢?上面只是绑定过程,别着急,下面就来探讨一下 SQL 语句的执行过程。

微信图片_20220412201311.jpg

有一部分代码被遮挡,代码有些多,不过不影响我们看主要流程

MapperProxyFactory 会生成代理对象,这个对象就是 MapperProxy,最终会调用到 mapperMethod.execute 方法,execute 方法比较长,其实逻辑比较简单,就是判断是 插入更新删除 还是 查询 语句,其中如果是查询的话,还会判断返回值的类型,我们可以点进去看一下都是怎么设计的。

微信图片_20220412201315.jpg

很多代码其实可以忽略,只看我标出来的重点就好了,我们可以看到,不管你前面经过多少道关卡处理,最终都逃不过 SqlSession 这个老大制定的标准。

我们以 selectList 为例,来看一下下面的执行过程。

微信图片_20220412201319.jpg

这是 DefaultSqlSession 中 selectList 的代码,我们可以看到出现了 executor,这是什么呢?我们下面来解释。

Executor

还记得我们之前的流程中提到了 Executor(执行器) 这个概念吗?我们来回顾一下它第一次出现的位置。

微信图片_20220412201323.jpg

由 Configuration 对象创建了一个 Executor 对象,这个 Executor 是干嘛的呢?下面我们就来认识一下

相关文章
|
SQL Java 测试技术
再也不需要手写 SQL 造数据了
DBeaver 是一个功能非常完善的数据库客户端,它有 开源免费版本:https://github.com/dbeaver/dbeaver, 企业版:https://dbeaver.com/
再也不需要手写 SQL 造数据了
|
5月前
|
SQL XML Java
后端数据库开发JDBC编程Mybatis之用基于XML文件的方式映射SQL语句实操
后端数据库开发JDBC编程Mybatis之用基于XML文件的方式映射SQL语句实操
72 3
|
5月前
|
SQL Java 数据库连接
MyBatis SQL 批量更新(代码➕案例)
MyBatis SQL 批量更新(代码➕案例)
915 0
|
SQL 缓存 Java
MyBatis核心源码深度剖析SQL执行过程
MyBatis核心源码深度剖析SQL执行过程
52 0
|
6月前
|
SQL 关系型数据库 MySQL
零基础快速自学SQL,2天足矣
零基础快速自学SQL,2天足矣
127 0
|
SQL NoSQL 算法
少侠!如何写一手好 SQL ?
少侠!如何写一手好 SQL ?
120 0
少侠!如何写一手好 SQL ?
|
SQL XML 缓存
面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他(一)
MyBatis 是第一个支持自定义 SQL、存储过程和高级映射的类持久框架。MyBatis 消除了大部分 JDBC 的样板代码、手动设置参数以及检索结果。MyBatis 能够支持简单的 XML 和注解配置规则。使 Map 接口和 POJO 类映射到数据库字段和记录。
172 0
面试官问你MyBatis SQL是如何执行的?把这篇文章甩给他(一)
|
SQL Java 数据库连接
Mybatis构建SQL——有码有依靠,有图有真相
Mybatis构建SQL——有码有依靠,有图有真相
198 0
Mybatis构建SQL——有码有依靠,有图有真相
|
SQL 缓存 Oracle
MyBatis 别再乱用 foreach 批量插入了,5000 数据用了 14 分钟,实力劝退。。
MyBatis 别再乱用 foreach 批量插入了,5000 数据用了 14 分钟,实力劝退。。
425 0
MyBatis 别再乱用 foreach 批量插入了,5000 数据用了 14 分钟,实力劝退。。
|
SQL 存储 缓存
如何写出一手好 SQL ?(2)
最近频繁出现慢SQL告警,执行时间最长的竟然高达5分钟。导出日志后分析,主要原因竟然是没有命中索引和没有分页处理 。
121 0