400-616-5551

您所在位置: 首页> IT培训> 面试官问你MyBatis SQL是如何执行的?(2)

面试官问你MyBatis SQL是如何执行的?(2)

发布百知教育 来源:IT培训 2020-02-04

重要配置

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

  • 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 详细了解。这里介绍几个平常使用过程中比较重要的配置

java培训班

<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,可以参考如下

java面试


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

具体做法为:实现 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 语句了。具体来看一下这个过程


java面试


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

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

MapperProxy

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


java面试

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

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

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

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


java培训班


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

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


java培训班

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

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


java培训班


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

Executor

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


java培训班


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

Executor 的继承结构

每一个 SqlSession 都会拥有一个 Executor 对象,这个对象负责增删改查的具体操作,我们可以简单的将它理解为 JDBC 中 Statement 的封装版。也可以理解为 SQL 的执行引擎,要干活总得有一个发起人吧,可以把 Executor 理解为发起人的角色。

首先先从 Executor 的继承体系来认识一下


java培训班



如上图所示,位于继承体系最顶层的是 Executor 执行器,它有两个实现类,分别是BaseExecutor CachingExecutor

BaseExecutor 是一个抽象类,这种通过抽象的实现接口的方式是适配器设计模式之接口适配 的体现,是Executor 的默认实现,实现了大部分 Executor 接口定义的功能,降低了接口实现的难度。BaseExecutor 的子类有三个,分别是 SimpleExecutor、ReuseExecutor 和 BatchExecutor。

SimpleExecutor : 简单执行器,是 MyBatis 中默认使用的执行器,每执行一次 update 或 select,就开启一个Statement 对象,用完就直接关闭 Statement 对象(可以是 Statement 或者是 PreparedStatment 对象)

ReuseExecutor : 可重用执行器,这里的重用指的是重复使用 Statement,它会在内部使用一个 Map 把创建的Statement 都缓存起来,每次执行 SQL 命令的时候,都会去判断是否存在基于该 SQL 的 Statement 对象,如果存在 Statement 对象并且对应的 connection 还没有关闭的情况下就继续使用之前的 Statement 对象,并将其缓存起来。因为每一个 SqlSession 都有一个新的 Executor 对象,所以我们缓存在 ReuseExecutor 上的 Statement作用域是同一个 SqlSession。

BatchExecutor : 批处理执行器,用于将多个 SQL 一次性输出到数据库

CachingExecutor: 缓存执行器,先从缓存中查询结果,如果存在就返回之前的结果;如果不存在,再委托给Executor delegate 去数据库中取,delegate 可以是上面任何一个执行器。

Executor 的创建和选择

我们上面提到 Executor 是由 Configuration 创建的,Configuration 会根据执行器的类型创建,如下


java培训班

这一步就是执行器的创建过程,根据传入的 ExecutorType 类型来判断是哪种执行器,如果不指定 ExecutorType ,默认创建的是简单执行器。它的赋值可以通过两个地方进行赋值:

  • 可以通过<settings>标签来设置当前工程中所有的 SqlSession 对象使用默认的 Executor
<settings>
<!--取值范围 SIMPLE, REUSE, BATCH -->
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
  • 另外一种直接通过Java对方法赋值的方式
session = factory.openSession(ExecutorType.BATCH);



上一篇:面试官问你MyBatis SQL是如何执行的?怎么回答?

下一篇:深圳java程序员待遇怎么样,就业机会多吗

相关推荐

关闭

立即申请

www.baizhiedu.com