关于java:Mybatis系列全解六Mybatis最硬核的API你知道几个

40次阅读

共计 20015 个字符,预计需要花费 51 分钟才能阅读完成。

封面:洛小汐
作者:潘潘

2020 年的大疫情,把世界撕成几片。

时至今日,仍旧人心惶惶。

很庆幸,身处这安稳国,

兼得一份安稳工。

·

东家常讲的一个词:深秋心态

大势时,不跟风、起哄,

萧条时,不放弃收获和耕耘的信念,

热时不燥、冷时不弃,

这就是深秋心态。

·

大疫情,置信只是大自然的法则,

也恰是咱们放弃深秋心态的时候,

默默收获和耕耘吧,

往年,世界会缓缓复苏,心愿都会降临。

2021 年要信念满满 ヾ(◍°∇°◍)ノ゙

定会收货满满 ~

上图保留可做朋友圈封面图 ~

前言

上节咱们介绍了《Mybatis 系列全解(五):全网最全!详解 Mybatis 的 Mapper 映射文件》,经此一文,咱们根本能把握 Mapper 映射器九大顶级元素的根本用法和其中技巧。在本节,咱们开始深刻,我筛选了 Mybatis 框架中几个比拟硬核的 API,跟大家一起探讨,夯实了这些 API,有助于你学习了解整个 Mybatis 框架,特地是 Mybatis 外围的数据处理层,你相对会造成一套清晰的脉络印记,总之,心愿大家都能成为 Mybatis King !

另外, 咱们的 Mybatis 全解系列始终在更新

Mybaits 系列全解 (继续更新)

  • Mybatis 系列全解(一):手写一套长久层框架
  • Mybatis 系列全解(二):Mybatis 简介与环境搭建
  • Mybatis 系列全解(三):Mybatis 简略 CRUD 应用介绍
  • Mybatis 系列全解(四):全网最全!Mybatis 配置文件 XML 全貌详解
  • Mybatis 系列全解(五):全网最全!详解 Mybatis 的 Mapper 映射文件
  • Mybatis 系列全解(六):Mybatis 最硬核的 API 你晓得几个?
  • Mybatis 系列全解(七):全息视角看 Dao 层两种实现形式之传统形式与代理形式
  • Mybatis 系列全解(八):Mybatis 的动静 SQL
  • Mybatis 系列全解(九):Mybatis 的简单映射
  • Mybatis 系列全解(十):Mybatis 注解开发
  • Mybatis 系列全解(十一):Mybatis 缓存全解
  • Mybatis 系列全解(十二):Mybatis 插件开发
  • Mybatis 系列全解(十三):Mybatis 代码生成器
  • Mybatis 系列全解(十四):Spring 集成 Mybatis
  • Mybatis 系列全解(十五):SpringBoot 集成 Mybatis
  • Mybatis 系列全解(十六):Mybatis 源码分析

本文目录

1、Mybatis 架构与外围 API

2、Configuration — 全局配置对象

3、Resources — 资源辅助类

4、SqlSessionFactoryBuilder — 会话工厂构建器

5、SqlSessionFactory — 会话工厂

6、SqlSession — 会话

7、Executor — 执行器

8、StatementHandler — 语句处理器

9、ParamerHandler — 参数处理器

10、ResultSetHandler — 后果集处理器

11、TypeHandler — 类型转换器

12、MappedStatement — 语句对象

13、SqlSource — SQL 源

14、BoundSql — SQL 语句

1、Mybatis 架构与外围 API

不出意外的话,在后续源码分析相干文章中,咱们会对 Mybatis 的源码进行一次大扫荡,一起开掘每一处值得大家深刻了解 / 记忆的知识点。而在本文中,咱们次要先把 Mybatis 的架构 / 档次铺开,仰视 Mybatis 架构的设计全貌,再把几个硬核的 API 具体消化。

整体程序脉络,心愿让你有所期待 ~

咱们先简略揭开 Mybatis 神秘的源码包,

瞅瞅 Ta 大抵目录构造:

看,Mybatis 的源代码包整齐划一排在 org.apache.ibatis 目录下,根本设计用处我简略梳理成下面这张图,不便大家直观了解,当然只看源码包目录构造,难免会显得干燥无物,所以咱们再看一下,其实 Mybatis 的源码包性能上能够是这么划分:

上图让咱们对 Mybatis 的架构有了形象的了解。

然而,实际上具体的职能分工,外围 API 的场景利用,到底会是怎么一套流程出现呢?

看上面这幅性能架构设计,或者你能更好的了解。

依据 Mybatis 性能架构咱们划分成三层

  • 接口层:该层提供一系列接口让用户直接参与应用,蕴含信息配置与理论数据操作调用。配置形式包含:基于 XML 配置形式、基于 Java API 配置形式两种形式,用户也能够通过接口层 API 对数据库发动增删改查等操作的申请,本层接口会把接管到的调用申请交给数据处理层的构件去解决。
  • 数据处理层:该层是 Mybatis 的核心层,负责数据处理,次要包含 SQL 参数映射解析、SQL 语句的理论执行、执行后果集的映射解决等。
  • 框架撑持层:该层属于 Mybatis 的后勤保障层,包含数据库连贯治理、事务把控治理、配置加载与缓存解决、日志解决、异样解决等,提供根底撑持能力,保障下层的数据处理。

咱们晓得,Mybatis 框架让用户只须要提供配置信息,并且专一于 SQL 的编写即可,对于连贯治理数据库 / 事务,或理论的 SQL 参数映射 / 语句执行 / 后果集映射等操作,作为用户都并不需要操心和参加。

然而,好奇的咱们其实想晓得,Mybatis 外围局部的数据处理在整体流程中,是如何撑持用户申请?同时各个构件之间交互,又是怎么流转呢?

很巧,我这里有一张图,介绍了整体流程:

依据以上框架流程图进行解说

  • 创立配置并调用 API:这一环节产生在应用程序端,是开发人员在理论应用程序中进行的两步操作,第一步创立外围配置文件 Configuration.xml 和映射文件 mapper.xml(通过注解形式也可创立以上两种配置),筹备好根底配置和 SQL 语句之后;第二步就是间接调用 Mybatis 框架中的数据库操作接口。
  • 加载配置并初始化:Mybatis 框架会依据应用程序端提供的外围配置文件与 SQL 映射文件的内容,应用资源辅助类 Resources 把配置文件读取成输出流,而后通过对应的解析器解析并封装到 Configuration 对象和 MappedStatement 对象,最终把对象存储在内存之中。
  • 创立会话并接管申请:在 Mybatis 框架加载配置并初始化配置对象之后,会话工厂构建器 SqlSessionFactoryBuilder 同时创立会话工厂 SqlSessionFactory,会话工厂会依据应用程序端的申请,创立会话 SqlSession,以便应用程序端进行数据库交互。
  • 解决申请:SqlSession 接管到申请之后,实际上并没有进行解决,而是把申请转发给执行器 Executor,执行器再分派到语句处理器 StatementHandler,语句处理器会联合参数处理器 ParameterHandler,进行数据库操作(底层封装了 JDBC Statement 操作)。
  • 返回处理结果:每个语句处理器 StatementHandler 解决实现数据库操作之后,会协同 ResultSetHandler 以及类型处理器 TypeHandler,对底层 JDBC 返回的后果集进行映射封装,最终返回封装对象。

针对以上总体框架流程波及到的这些硬核 API,上面咱们一一开展介绍,但不会具体分析源码与原理,包含构建细节,因为这些咱们在后续的源码分析章节中都会详细分析。

2、Configuration — 全局配置对象

对于 Mybatis 的全局配置对象 Configuration,我置信无论是初学者还是资深玩家,都不会生疏。整个 Mybatis 的宇宙,都围绕着 Configuration 转。Configuration 对象的构造和 config.xml 配置文件的内容简直雷同,涵盖了 properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理器),objectFactory (对象工厂),mappers (映射器)等等,之前咱们有专门的一篇文章具体进行介绍,感兴趣的敌人往上翻到目录列表,找到 《Mybatis 系列全解(四):全网最全!Mybatis 配置文件 XML 全貌详解》 一文具体品尝一番吧。

配置对象 Configuration 通过解析器 XMLConfigBuilder 进行解析,把全局配置文件 Config.xml 与 映射器配置文件 Mapper.xml 中的配置信息全副构建成残缺的 Configuration 对象,后续咱们源码剖析时具体分析整个过程。

3、Resources — 资源辅助类

咱们晓得,像 Configuration 和 Mapper 的配置信息寄存在 XML 文件中,Mybatis 框架在构建配置对象时,必须先把 XML 文件信息加载成流,再做后续的解析封装,而 Resources 作为资源的辅助类,恰好干的就是这个活,无论是通过加载本地资源或是加载近程资源,最终都会通过 类加载器 拜访资源文件并输入文件流。


// 加载外围配置文件
InputStream resourceAsStream = 
    Resources.getResourceAsStream("Config.xml");

Resources 实实在在提供了一系列办法分分钟解决你的文件读取加载问题:

4、SqlSessionFactoryBuilder — 会话工厂构建器

咱们一撞见 xxxBuilder,就大抵能晓得它是某类对象的构建器,这里 SqlSessionFactoryBuilder 也是一样,它是 Mybatis 中的一个会话工厂构建器,在资源辅助类 Resources 读取到文件流信息之后,它负责解析文件流信息并构建会话工厂 SqlSessionFactory。(解析的配置文件蕴含:全局配置 Configuration 与映射器 Mapper)

在程序利用端,咱们个别应用 SqlSessionFactoryBuilder 间接构建会话工厂:


// 取得 sqlSession 工厂对象
SqlSessionFactory sqlSessionFactory = 
    new SqlSessionFactoryBuilder().build(resourceAsStream);

当然,如果你集成了 Spring 框架的我的项目,则不须要本人手工去构建会话工厂,间接在 Spring 配置文件中指定即可,例如指定一个 bean 对象,id 是 sqlSessionFactory,而 class 类指定为 org.mybatis.spring.SqlSessionFactoryBean。

SqlSessionFactoryBuilder 外部通过解析器 XMLConfigBuilder 解析了文件流,同时封装成为配置对象 Configuration,再把 Configuration 对象进行传递并构建实例。


public SqlSessionFactory build(
    InputStream inputStream, 
    String environment, 
    Properties properties) {
     
      // 配置解析器解析
      XMLConfigBuilder parser = 
          new XMLConfigBuilder(inputStream,environment, properties);
    
      // 最终实例会话工厂
      return build(parser.parse()); 
    
}

最终实例会话工厂,其实 Mybatis 默认实现是 new 了一个 DefaultSqlSessionFactory 实例。


// 最终实例会话工厂
public SqlSessionFactory build(Configuration config) {return new DefaultSqlSessionFactory(config);
}

会话工厂构建器 SqlSessionFactoryBuilder 利用了构建者模式,次要目标就是为了构建 SqlSessionFactory 对象,以便后续生产 SqlSession 对象,这个结构器基本上算是 Mybatis 框架的入口构建器,它提供了一系列多态办法 build(),反对用户应用 XML 配置文件或 Java API(Properties)来构建会话工厂 SqlSessionFactory 实例。

SqlSessionFactoryBuilder 的毕生只为成就 SqlSessionFactory,当 SqlSessionFactory 一经实例,SqlSessionFactoryBuilder 使命实现,便可沦亡,便可被抛弃。

因而 SqlSessionFactoryBuilder 实例的最佳作用域是 办法作用域(也就是部分办法变量)。你能够重用 SqlSessionFactoryBuilder 来创立多个 SqlSessionFactory 实例,但最好不要始终保留着它,以保障所有的 XML 解析资源能够被开释给更重要的事件。

SqlSessionFactoryBuilder 中灵便构建会话工厂的一系列接口:

5、SqlSessionFactory — 会话工厂

会话工厂 SqlSessionFactory 是一个接口,作用是生产数据库会话对象 SqlSession,有两个实现类:

  • DefaultSqlSessionFactory (默认实现)
  • SqlSessionManager (仅多实现了一个 Sqlsession 接口,已弃用)

在介绍会话工厂构建器 SqlSessionFactoryBuilder 的时候,咱们理解到构建器默认创立了 DefaultSqlSessionFactory 实例,并且会话工厂自身会绑定一个重要的属性 Configuration 对象,在生产会话时,最终也会把 Configuration 配置对象传递并设置到会话 SqlSession 上。

会话工厂能够简略创立 SqlSession 实例:


// 创立 SqlSession 实例
SqlSession session = sqlSessionFactory.openSession();

会话工厂创立 SqlSession 时,会绑定数据源、事务处理、执行器等等,默认会话工厂实现类 DefaultSqlSessionFactory 在创立会话对象时,最终都会调用 openSessionFromDataSource 办法,即是如此实现:


// 每一个 openSession 最终都会调用此处
private SqlSession openSessionFromDataSource(
    ExecutorType execType, 
    TransactionIsolationLevel level, 
    boolean autoCommit) {
    
    // 环境配置
    final Environment environment = 
        configuration.getEnvironment();
    
    // 事务工厂
    final TransactionFactory transactionFactory = 
        getTransactionFactoryFromEnvironment(environment);
    
    // 事务
    Transaction tx =
        transactionFactory.newTransaction(environment.getDataSource(), 
        level, 
        autoCommit);
    
    // 执行器
    final Executor executor = 
        configuration.newExecutor(tx, execType);
    
    // 最终生成会话
    return new DefaultSqlSession(configuration, executor, autoCommit);
    
  }

另外,会话工厂其实提供了一系列接口来灵便生产会话 SqlSession,你能够指定:

  • 事务处理:你心愿在 session 作用域中应用 / 开启事务作用域(也就是不主动提交事务),还是应用主动提交(auto-commit),sqlSession 默认不提交事务,对于增删改操作时须要手动提交事务。
  • 数据库连贯:你心愿 MyBatis 帮你从已配置的数据源获取连贯,还是应用本人提供的连贯,能够动态创建数据源对象 Connection。
  • 执行器类型:你心愿指定某类执行器来创立 / 执行 / 预处理语句,能够有一般执行器(SimpleExecutor),或复用执行器(ReuserExecutor)、还是批量执行器(BatchExecutor)等,上面介绍执行器时会具体阐明。
  • 事务隔离级别反对:反对 JDBC 的五个隔离级别(NONE、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ 和 SERIALIZABLE),对于事务相干的内容,咱们后续 《spring 系列全解》 会具体讲,这里简略阐明一下就是事务隔离级别次要为了解决例如脏读、不可反复读、幻读等问题,应用不同的事务隔离级别势必会导致不同的数据库执行效率,因而咱们再不同的零碎 / 性能中,对隔离级别有不同的需要。

SqlSessionFactory 一旦被创立就应该在 利用的运行期间 始终存在,没有任何理由抛弃它或从新创立另一个实例。应用 SqlSessionFactory 的最佳实际是在利用运行期间不要反复创立屡次,屡次重建 SqlSessionFactory 被视为一种代码“坏习惯”。因而 SqlSessionFactory 的最佳作用域是 利用作用域。最简略的就是应用单例模式或者动态单例模式。

请记住,创立 SqlSessionFactory,一次就好!

每个数据库对应一个 SqlSessionFactory 实例。SqlSessionFactory 一旦被创立,它的生命周期应该与利用的生命周期雷同。所以,如果你想连贯两个数据库,就须要创立两个 SqlSessionFactory 实例,每个数据库对应一个;而如果是三个数据库,就须要三个实例,依此类推。

6、SqlSession — 会话

SqlSession 是一个接口,有两个实现类:

  • DefaultSqlSession(默认实现)
  • SqlSessionManager(已弃用)

简略来说,通过会话工厂构建出 SqlSession 实例之后,咱们就能够进行增删改查了,默认实例 DefaultSqlSession 提供了如此多的办法供用户应用,有超过 30 个:

sqlSession 的办法除了 CURD,还提供了事务的管制例如提交 / 敞开 / 回滚等、提供了配置对象的获取例如 getConfiguration()、提供了批量语句的执行更新例如 flushStatements()、提供了缓存革除例如 clearCache()、提供了映射器的应用 getMapper() 等等。

对于客户端利用层面来说,相熟 sqlSession 的 API 根本就能够任意操作数据库了,不过咱们心愿想进一步理解 sqlSession 外部是如何执行 sql 呢?其实 sqlSession 是 Mybatis 中用于和数据库交互的 顶层类 ,通常将它与本地线程 ThreadLocal 绑定,一个会话应用一个 SqlSession,并且在应用结束之后进行 敞开

之所以称 SqlSession 为数据交互的 顶层类,是它其实没有实现本质的数据库操作。依据之前的架构设计流程咱们曾经清晰的晓得,SqlSession 对数据库的操作都会转发给具体的执行器 Executor 来实现;当然执行器也是甩手掌柜,执行器 Executor 会再分派给语句处理器 StatementHandler,语句处理器会联合参数处理器 ParameterHandler,共同完成最终的数据库执行解决操作(底层还是封装了 JDBC Statement 操作)。并在每个语句处理器 StatementHandler 解决实现数据库操作之后,通过后果结处理器 ResultSetHandler 以及类型处理器 TypeHandler,对底层 JDBC 返回的后果集进行映射封装,最终才返回预期的封装对象。

关注以下图示 sqlSession 红色高亮地位,详细描述了会话的理论执行门路:

SqlSession 能够了解为一次数据库会话,一次会话当中既能够执行一次 sql,也容许你批量执行屡次,然而一旦会话敞开之后想要再执行 sql,那就必须从新创立会话。

每个线程都应该有它本人的 SqlSession 实例,SqlSession 的实例不是线程平安的,因而是不能被共享的,所以它的最佳的作用域是 申请(request)或办法(method) 作用域。相对不能将 SqlSession 实例的援用放在一个类的动态域,甚至一个类的实例变量也不行。也绝不能将 SqlSession 实例的援用放在任何类型的托管作用域中,比方 Servlet 框架中的 HttpSession。如果你当初正在应用一种 Web 框架,思考将 SqlSession 放在一个和 HTTP 申请类似的作用域中。换句话说,每次收到 HTTP 申请,就能够关上一个 SqlSession,返回一个响应后,就敞开它。这个敞开操作很重要,为了确保每次都能执行敞开操作,你应该把这个敞开操作放到 finally 块中。

Spring 集成 Mybatis 之后,通过依赖注入能够创立线程平安的、基于事务的 SqlSession,并治理他们的生命周期,举荐搭配应用。

7、Executor — 执行器

Executor 是一个执行器接口,是 Mybatis 的调度外围,它定义了一组治理 Statement 对象与获取事务的办法,并负责 SQL 语句的生成和一级 / 二级查问缓存的保护等,SqlSessionFactory 在创立 SqlSession 时会同时创立执行器,并指定执行器类型,默认应用 SimpleExecutor。执行器接口有 5 个子孙实现类,其中 BaseExecutor 是抽象类,另外 4 个子孙实现类别离是:SimpleExecutor、BatchExecutor、ReuseExecutor、CachingExecutor。

  • BaseExecutor:根底执行器(抽象类),对 Executor 接口进行了根本实现,为下一级实现类执行器提供根底反对。BaseExecutor 有三个子类别离是 SimpleExecutor、ResuseExecutor、BatchExecutor。
  • SimpleExecutor:一般执行器,继承 BaseExecutor 抽象类,是 MyBatis 中 默认 应用的执行器. 每执行一次 update 或 select,就开启一个 Statement 对象,用完立即敞开 Statement 对象。(能够是 Statement 或 PrepareStatement 对象)。
  • ReuseExecutor:复用执行器,继承 BaseExecutor 抽象类,这里的复用指的是重复使用 Statement . 它会在外部利用一个 Map 把创立的 Statement 都缓存起来,每次在执行一条 SQL 语 句时,它都会去判断之前是否存在基于该 SQL 缓存的 Statement 对象,存在且之前缓存的 Statement 对象对应的 Connection 还没有敞开则会持续应用之前的 Statement 对象,否则将创立一个新的 Statement 对象,并将其缓存起来。因为每一个新的 SqlSession 都有一个新的 Executor 对象,所以咱们缓存在 ReuseExecutor 上的 Statement 的作用域是同一个 SqlSession。
  • BatchExecutor:批处理执行器,继承 BaseExecutor 抽象类,通过批量操作来进步性能,用于将多个 sql 语句一次性输送到数据库执行。因为外部有缓存的实现,所以应用实现后须要调用 flushStatements() 来革除缓存。
  • CachingExecutor:缓存执行器,继承 BaseExecutor 抽象类,它为 Executor 对象减少了 二级缓存 的相干性能,cachingExecutor 有一个重要属性 delegate,即为委托的执行器对象,能够是 SimpleExecutor、ReuseExecutor、BatchExecutor 中任意一个。CachingExecutor 在执行数据库 update 操作时,它间接调用 委托对象 delegate 的 update 办法;而执行查问时,它会先从缓存中获取查问后果,存在就返回,不存在则委托 delegate 去数据库取,而后存储到缓存 cache 中。

Mybatis 在构建 Configuration 配置类时默认把 ExecutorType.SIMPLE 作为执行器类型,当咱们的会话工厂 DefaultSqlSessionFactory 开始生产 SqlSession 会话时,会同时构建执行器,此时就会根据配置类 Configuration 构建时指定的执行器类型来实例具体执行器,流程如下:

// 1、Configuration 配置类构建时
//   指定了默认执行器类型为:一般执行器
protected ExecutorType defaultExecutorType 
    = ExecutorType.SIMPLE;

// 2、Configuration 配置类中
//   提供办法获取默认执行器类型
public ExecutorType getDefaultExecutorType() {return defaultExecutorType;}

// 3、会话工厂创立 SqlSession 实例时
SqlSession session = sqlSessionFactory.openSession();

// 4、openSession 理论逻辑
public SqlSession openSession() {
    return 
       openSessionFromDataSource(
           // 这里可就获取了默认执行器
           configuration.getDefaultExecutorType(), 
           null, 
           false
        );
}  

这里,必定有人想晓得,咱们是否指定其它执行器呢?

答案是:当然能够,有两种形式指定:

  • 第一种形式是通过 Java API 指定,在开启会话 openSession 时进行指定。例如:

// 创立 SqlSession 实例
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE)
    
// ExecutorType 是一个枚举
// 有三个值 SIMPLE, REUSE, BATCH
  • 另一种通过 Configuration 配置形式来指定默认执行器类型。例如
<settings>
    <!-- 取值范畴 SIMPLE, REUSE, BATCH -->
    <setting name="defaultExecutorType" value="REUSE"/>
</settings>

对于第二种 settings 的配置形式,其实之前咱们在介绍 Mybatis 的配置文件中曾经讲过,这里再简略阐明一下,像下面配置 settings 中的属性 defaultExecutorType,根本这些属性都是 Mybatis 额定提供给咱们灵便设置的,就算咱们不设置 Mybatis 也会有默认值,例如像 defaultExecutorType 的默认值就是 SIMPLE。你看一下 Mybatis 在解析 Configuration 配置时的默认构建,就会明确:

解析 Configuration 的解析器(类与具体方法的代码门路):

org.apache.ibatis.builder.xml.XMLConfigBuilder#settingsElement

咱们截取局部代码逻辑,上面是设置默认执行器类型属性 defaultExecutorType 的内容:


// 配置文件解析器 
public class XMLConfigBuilder {
    
    // 最终解析到的 Configuration 对象
    protected final Configuration configuration;
    
    // 为 Configuration 对象设置属性
    private void settingsElement(Properties props) {
        
        // 设置默认执行器类型,默认是 SIMPLE
        configuration.setDefaultExecutorType(
            ExecutorType.valueOf(
                props.getProperty("defaultExecutorType", "SIMPLE")));
        
        // .... 当然这里还有很多属性设置
        // .... 只有你在 <settings> 中配置即可
    
    }
}

留神,到此咱们晓得能够依据业务须要指定执行器类型,例如 SIMPLE(一般执行器), REUSE(复用执行器), BATCH(批处理执行器)。

然而,有敌人就好奇了,那缓存执行器 CachingExecutor 如同不见阐明呢?

的确如此,因为缓存执行器个其它三个执行器还不太一样,CachingExecutor 是须要咱们开启二级缓存才会有,这里大家先不要思考什么是一级缓存,什么二级缓存,后续咱们有一文会具体讲缓存整个常识内容。

大家先理解一个概念即可,就是 Mybatis 的一级缓存是默认开启的,不论你要不要,都会有一级缓存,而二级缓存呢,是默认敞开的,但容许咱们手工开启。

比照开启二级缓存前后,执行器执行的区别吧!

  • 不开启二级缓存,执行器执行时

  • 开启二级缓存之后,执行器执行时

其实咱们实际操作数据库,不会间接接触到执行器 Executor,不过咱们的确能够理解一下根本的执行原理,上面列出了执行器接口提供的泛滥重载办法,根本用于事务 / 缓存 / 数据库治理与拜访,能够晓得一下:

到此,对于执行器有了根本的意识,然而实际上,咱们晓得执行器本身没有去具体执行 SQL 语句,而是分派到语句处理器 StatementHandler,语句处理器会联合参数处理器 ParameterHandler,最终进行数据库操作。

8、StatementHandler — 语句处理器

StatementHandler 是一个语句处理器接口,它封装了 JDBC Statement 操作,负责对 JDBC Statement 的操作,如 设置参数、后果集映射,是理论跟数据库做交互的一道。StatementHandler 语句处理器实例,是在执行器具体执行 CRUD 操作时构建的,默认应用 PrepareStatementHandler。语句处理器接口有 5 个子孙实现类,其中 BaseStatementHandler 是抽象类,另外 4 个子孙实现类别离是:SimpleStatementHandler、PrepareStatementHandler、CallableStatementHandler、RoutingStatementHandler。

  • BaseStatementHandler:根底语句处理器(抽象类),它根本把语句处理器接口的外围局部都实现了,包含配置绑定、执行器绑定、映射器绑定、参数处理器构建、后果集处理器构建、语句超时设置、语句敞开等,并另外定义了新的办法 instantiateStatement 供不同子类实现以便获取不同类型的语句连贯,子类能够一般执行 SQL 语句,也能够做预编译执行,还能够执行存储过程等。
  • SimpleStatementHandler:一般语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.Statement 对象的解决,解决一般的不带动静参数运行的 SQL,即执行简略拼接的字符串语句,同时因为 Statement 的个性,SimpleStatementHandler 每次执行都须要编译 SQL(留神:咱们晓得 SQL 的执行是须要编译和解析的)。
  • PreparedStatementHandler:预编译语句处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.PrepareStatement 对象的解决,相比下面的一般语句处理器,它反对可变参数 SQL 执行,因为 PrepareStatement 的个性,它会进行预编译,在缓存中一旦发现有预编译的命令,会间接解析执行,所以缩小了再次编译环节,可能无效进步零碎性能,并预防 SQL 注入攻打(所以是零碎默认也是咱们举荐的语句处理器)。
  • CallableStatementHandler:存储过程处理器,继承 BaseStatementHandler 抽象类,对应 java.sql.CallableStatement 对象的解决,很明了,它是用来调用存储过程的,减少了存储过程的函数调用以及输入 / 输出参数的解决反对。

其实一般语句处理器、预执行语句处理器以及存储过程处理器,只是 Mybatis 对于 JDBC 的语句执行对象的简略包装而已,没有特地神秘,看以下 JDBC 的语句执行对象的类图关系也就可能分明。

  • RoutingStatementHandler:路由语句处理器,间接实现了 StatementHandler 接口,作用如其名称,确确实实只是起到了路由性能,并把下面介绍到的三个语句处理器实例作为本身的委托对象而已,所以执行器在构建语句处理器时,都是间接 new 了 RoutingStatementHandler 实例。
// 1、执行器构建语句处理器实例
public StatementHandler newStatementHandler(...) {
    
    // 构建路由语句处理器即可!StatementHandler statementHandler = 
        new RoutingStatementHandler(...);
    
    // 其它逻辑疏忽...
    return statementHandler;
}

// 2、理论构造方法(路由关系)public RoutingStatementHandler(...) {

    // 依据指定类型结构委托实例
    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(...);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(...);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(...);
        break;
      default:
        throw new ExecutorException(
            "Unknown statement type:" 
                + ms.getStatementType());
    }
}

咱们后面介绍了执行器具体执行 CRUD 操作时,结构的语句处理器默认应用 PrepareStatementHandler,不过有些好奇的脑袋就想问问,那咱们能不能指定语句处理器类型呢?

当然能够,例如咱们指定更新用户语句实用预编译解决语句处理器:

<!-- 取值范畴 STATEMENT, PREPARED, CALLABLE -->
<update id="updateUser" statementType="STATEMENT">
    update t_user set name = #{newName}
</update>

当 Mybatis 在解析映射器中的每条语句时,会设置语句处理器类型:

// 语句对象解析器 
public class XMLStatementBuilder {
    
    // 解析语句节点
    public void parseStatementNode() { 
        
        // 设置语句处理器类型
        // 默认是 PREPARED 类型
        StatementType statementType = 
         StatementType.valueOf(
            context.getStringAttribute(
                "statementType", 
                StatementType.PREPARED.toString())
        ); 
    }  
}

所以,语句执行器与数据库的交互过程:

当然,语句处理器接口 StatementHandler 提供了根本接口,个别咱们没必要自定义实现类,所以能够简略看一下即可:

9、ParamerHandler — 参数处理器

ParameterHandler 是一个参数处理器接口,它负责把用户传递的参数转换成 JDBC Statement 所须要的参数,底层做数据转换的工作会交给类型转换器 TypeHandler,前面会介绍。

很显然,须要对传入的参数进行转换解决的 StatementHandler 实例只有两个,别离是:

  • PrepareStatementHandler 预编译处理器
  • CallableStatementHandler 存储过程处理器

下面在介绍语句处理器的时候,咱们有介绍说根底语句处理器 BaseStatementHandler 在进行实例构建时,会同时构建参数处理器与后果集处理器,所以参数处理器就是在此时被构建。

// 根底语句处理器
public abstract class BaseStatementHandler{
    
    // 结构实例时
    protected BaseStatementHandler(...){
        
        // 其它逻辑可疏忽...
        
        // 1、构建参数处理器
        this.parameterHandler = 
            conf.newParameterHandler(...);
        
        // 2、构建后果集处理器
        this.resultSetHandler = 
            conf.newResultSetHandler(...);
    } 
}

对于参数处理器接口,绝对简略,只有 1 个默认实现类 DefaultParameterHandler,该接口只有两个办法,别离是:

  • 1、setParameters 设置参数,产生在 CURD 语句执行时,语句处理器设置参数
// 有 2 个处理器会应用,别离是:// 预编译处理器 PreparedStatementHandler
// 存储过程处理器 CallableStatementHandler

public void parameterize(Statement statement) {
    
    // 应用 ParameterHandler 对象来实现对 Statement 的设值
    parameterHandler.setParameters(statement);
}

利用场景例如查问用户对象时,设置姓名,参数处理器联合类型处理器把 name 属性占位符进行赋值。

<select id="queryUSer">
    select * from t_user where name = #{name}
</select>
  • 2、getParameterObject 获取参数,产生在后果集返回时,后果集处理器获取对象参数,值得注意的时,该办法只用于存储过程处理器 CallableStatementHandler。
// 默认后果集处理器
public class DefaultResultSetHandler{
   
    // 解决输入参数
    public void handleOutputParameters(...) {
    
        // 获取参数
        final Object parameterObject = 
            parameterHandler.getParameterObject();
    
        // 其它存储过程输入参数解决逻辑...
    }    
}

10、ResultSetHandler — 后果集处理器

ResultSetHandler 是一个后果集处理器接口,它负责负责将 JDBC 返回的后果集 resultSet 对象转换为 List 类型的汇合,是在语句处理器构建实例时被同时创立,底层做数据转换的工作会交给类型转换器 TypeHandler,它有 1 个默认实现类 DefaultResultSetHandler,该接口有 3 个办法,别离是:

  • handleResultSets:负责后果集解决,实现映射返回后果对象
  • handleCursorResultSets:负责游标对象解决
  • handleOutputParameters:负责存储过程的输入参数解决

后果集处理器对于 JDBC 返回的后果集的根本解决,是先获取咱们在映射器 Mapper 中指定 resultType 或 resultMap 映射关系,而后遍历解析后果集中的每一列数据,底层通过 MetaObject 对象做相干的反射解决。

对于具体的源码逻辑,咱们后续源码分析局部具体讲。

不讲不是中国人 O(∩_∩)O ~

11、TypeHandler — 类型转换器

TypeHandler 是一个类型转换器 / 处理器接口,它负责 Java 数据类型和 JDBC 数据类型之间的映射与转换,当对 Statement 对象设置参数时,由 JavaType 转换为 JdbcType,当对 Statement 返回后果集进行封装映射时,又会将 JdbcType 转换为 JavaType。

个别,咱们能够间接应用 Mybatis 内置的类型处理器,简略看了一下有 65+ 个,当然咱们是能够依据业务须要自定义类型处理器的,以便解决简单类型或非标类型。

具体做法为:

1、实现 org.apache.ibatis.type.TypeHandler 接口;

2、继承 org.apache.ibatis.type.BaseTypeHandler 类。

其中 BaseTypeHandler 类作为抽象类就曾经实现了 TypeHandler 接口。

咱们看到接口 TypeHandler 定义了四个办法:

public interface TypeHandler<T> {

  // 设置参数
  void setParameter(
      PreparedStatement ps, 
      int i, T parameter, 
      JdbcType jdbcType);

  // 依据列名获取转换后果
  T getResult(ResultSet rs, String columnName);

  // 依据列下标获取转换后果
  T getResult(ResultSet rs, int columnIndex);

  // 依据列下标获取【存储过程】的输入后果
  T getResult(CallableStatement cs, int columnIndex);

}

其实,我之前在介绍 Mybatis 外围配置的时候,有鼎力介绍过类型处理器,没必要反复写(其实是懒),感兴趣的敌人能够间接看咱们之前的文章 《Mybatis 系列全解(四):全网最全!Mybatis 配置文件 XML 全貌详解》中对类型处理器 TypeHandler 的介绍。

12、MappedStatement — 语句对象

MappedStatement 语句对象,就是咱们在映射器 Mapper 中保护的每一条语句,例如 <select|update|delete|insert>,Mybatis 中通过语句结构器 XMLStatementBuilder 对每一个语句进行解析:

整个解析过程分为 4 步骤:

  • 1、配置解析器 XMLConfigBuilder 解析映射器:
// Configuration 配置解析器
public class XMLConfigBuilder{
    
    // 解析映射器
    private void mapperElement(){
        
        // 创立映射器解析实例
        XMLMapperBuilder mapperParser = 
            new XMLMapperBuilder(...);
        
        // 开始解析
        mapperParser.parse();}
}

2、映射对象解析器 XMLMapperBuilder 解析语句

// 映射对象解析器
public class XMLMapperBuilder{
    
    // 1、解析入口
    public void parse() { 
        
        // 解析映射器文件
        configurationElement(parser.evalNode("/mapper"));
    }
    
    // 2、节点解析
    private void configurationElement(XNode context) {
        
        // 构建语句对象
        buildStatementFromContext(
            context.evalNodes("select|insert|update|delete"));
    }
    
    // 3、最终调用语句解析器
    private void buildStatementFromContext(){
        
        // 创立语句解析实例
        XMLStatementBuilder statementParser = 
            new XMLStatementBuilder();
        
        // 解析语句节点
        statementParser.parseStatementNode();}
}

3、语句解析器 XMLStatementBuilder 解析每一个节点

// 语句解析器
public class XMLStatementBuilder{
    
    // 解析语句节点
    public void parseStatementNode() {
        
        // 通过语句辅助类构建语句对象
        builderAssistant.addMappedStatement(...)
    }
}

4、语句辅助类 MapperBuilderAssistant 增加进语句汇合中

// 语句辅助类
public class MapperBuilderAssistant{
    
    // 增加语句对象
    public MappedStatement addMappedStatement(
         
        // 最终增加到配置类的语句汇合中
        configuration.addMappedStatement(statement);
    }
}

13、SqlSource — SQL 源

SqlSource 是一个 SQL 源接口,它会联合用户传递的参数对象 parameterObject,动静地生成 SQL 语句,并最终封装成 BoundSql 对象。SqlSource 接口有 5 个实现类,别离是:StaticSqlSource、DynamicSqlSource、RawSqlSource、ProviderSqlSource、VelocitySqlSource(这只是一个测试用例,而非真正模板 Sql 源实现类)。

  • StaticSqlSource:动态 SQL 源实现类,所有的 SQL 源最终都会构建 StaticSqlSource 实例,该实现类会生成最终可执行的 SQL 语句供 statement 或 prepareStatement 应用。
  • RawSqlSource:原生 SQL 源实现类,解析构建含有 ‘#{}’ 占位符的 SQL 语句或原生 SQL 语句,解析完最终会构建 StaticSqlSource 实例。
  • DynamicSqlSource:动静 SQL 源实现类,解析构建含有 ‘${}’ 替换符的 SQL 语句或含有动静 SQL 的语句(例如 If/Where/Foreach 等),解析完最终会构建 StaticSqlSource 实例。
  • ProviderSqlSource:注解形式的 SQL 源实现类,会依据 SQL 语句的内容分发给 RawSqlSource 或 DynamicSqlSource,当然最终也会构建 StaticSqlSource 实例。
  • VelocitySqlSource:模板 SQL 源实现类,官网申明这只是一个测试用例,而非真正的模板 Sql 源实现类。

SqlSource 实例在配置类 Configuration 解析阶段就被创立,Mybatis 框架会根据 3 个维度的信息来抉择构建哪种数据源实例:

  • 第一个维度:客户端的 SQL 配置形式:XML 形式或者注解形式。
  • 第二个维度:SQL 语句中是否应用动静 SQL(if/where/foreach 等)。
  • 第三个维度:SQL 语句中是否含有替换符 ‘${}’ 或占位符 ‘#{}’。

SqlSource 接口只有一个办法 getBoundSql,就是创立 BoundSql 对象。

public interface SqlSource {BoundSql getBoundSql(Object parameterObject);

}

14、BoundSql — SQL 语句

BoundSql 对象存储了动静生成的 SQL 语句以及相应的参数信息,BoundSql 对象是在执行器具体执行 CURD 时通过理论的 SqlSource 实例所构建。通过 BoundSql 可能获取到理论数据库执行的 SQL 语句,零碎可依据 SQL 语句构建 Statement 或者 PrepareStatement。

public class BoundSql {

  // 该字段中记录了 SQL 语句,该 SQL 语句中可能含有 "?" 占位符
  private final String sql;
    
  //SQL 中的参数属性汇合
  private final List<ParameterMapping> parameterMappings;
    
  // 客户端执行 SQL 时传入的理论参数值
  private final Object parameterObject;
    
  // 复制 DynamicContext.bindings 汇合中的内容
  private final Map<String, Object> additionalParameters;
    
  // 通过 additionalParameters 构建元参数对象
  private final MetaObject metaParameters;
    
}

总结

本文整整 2 周才根本修整欠缺,顺着 Mybatis 的数据库外围执行流程,咱们大抵介绍了 Mybatis 中几个绝对外围的 API,咱们是一边构建外围架构功能设计图示,一边梳理 API 相干常识脉络,目前根本算是捋清。

其中比拟苦恼的问题就是,对于文章内容范畴尺度的把控,每篇文章我都心愿讲得全面,讲得粗疏,这两个保持就注定了文章的内容不可能全是干货,导致略懂的人只能抉择跳跃式浏览,对于有养分的知识点须要挑挑拣拣,自我取舍;同时还会导致内容篇幅巨长,导致整体浏览耗时过长,不易疾速排汇。

后续尝试扭转一下写作形式,尽量输入干货、内容点到为止,对于须要巨细分析的内容,咱们单立文章解读。

本篇完,本系列下一篇咱们讲《Mybatis 系列全解(七):Dao 层两种实现形式》。

文章继续更新,微信搜寻「潘潘和他的敌人们 」第一工夫浏览,随时有惊喜。本文会在 GitHub https://github.com/JavaWorld 收录,对于热腾腾的技术、框架、面经、解决方案、摸鱼技巧、教程、视频、漫画等等等等,咱们都会以最美的姿态第一工夫送达,欢送 Star ~ 咱们将来 不止文章 !想进读者群的敌人欢送撩我集体号:panshenlian,备注「 加群」咱们群里畅聊,BIU ~

正文完
 0