封面:洛小汐
做者:潘潘java
2020 年的大疫情,把世界撕成几片。git
时至今日,依旧人心惶惶。github
很庆幸,身处这安稳国,spring
兼得一份安稳工。sql
·数据库
东家常讲的一个词:深秋心态 。apache
大势时,不跟风、起哄,缓存
萧条时,不放弃播种和耕耘的信心,安全
热时不燥、冷时不弃,微信
这就是深秋心态。
·
大疫情,相信只是大天然的规律,
也恰是咱们保持深秋心态的时候,
默默播种和耕耘吧,
今年,世界会慢慢复苏,但愿都会来临。
2021年要信心满满 ヾ(◍°∇°◍)ノ゙
定会收货满满 ~
上图保存可作朋友圈封面图 ~
上节咱们介绍了 《 Mybatis系列全解(五):全网最全!详解Mybatis的Mapper映射文件 》,经此一文,咱们基本能掌握 Mapper 映射器九大顶级元素的基本用法和其中技巧。在本节,咱们开始深刻,我挑选了 Mybatis 框架中几个比较硬核的 API ,跟你们一块儿探讨,夯实了这些 API ,有助于你学习理解整个 Mybatis 框架,特别是 Mybatis 核心的数据处理层,你绝对会造成一套清晰的脉络印记,总之,但愿你们都能成为 Mybatis King !
另外, 咱们的 Mybatis 全解系列一直在更新
一、Mybatis 架构与核心API
二、Configuration -- 全局配置对象
三、Resources -- 资源辅助类
四、SqlSessionFactoryBuilder -- 会话工厂构建器
五、SqlSessionFactory -- 会话工厂
六、SqlSession -- 会话
七、Executor -- 执行器
八、StatementHandler -- 语句处理器
九、ParamerHandler -- 参数处理器
十、ResultSetHandler -- 结果集处理器
十一、TypeHandler -- 类型转换器
十二、MappedStatement -- 语句对象
1三、SqlSource -- SQL源
1四、BoundSql -- SQL语句
不出意外的话,在后续源码剖析相关文章中,咱们会对 Mybatis 的源码进行一次大扫荡,一块儿挖掘每一处值得你们深刻理解/记忆的知识点。而在本文中,咱们主要先把 Mybatis 的架构/层次铺开,俯视 Mybatis 架构的设计全貌,再把几个硬核的 API 详细消化。
总体顺序脉络,但愿让你有所期待 ~
咱们先简单揭开 Mybatis 神秘的源码包,
瞅瞅 Ta 大体目录结构 :
看,Mybatis 的源代码包整齐划一排在 org.apache.ibatis 目录下,基本设计用途我简单梳理成上面这张图,方便你们直观理解,固然只看源码包目录结构,不免会显得枯燥无物,因此咱们再看一下,其实 Mybatis 的源码包功能上能够是这么划分:
上图让咱们对 Mybatis 的架构有了抽象的理解。
然而,实际上具体的职能分工,核心 API 的场景应用,到底会是怎样一套流程呈现呢?
看下面这幅功能架构设计,或许你能更好的理解。
根据 Mybatis 功能架构咱们划分红三层:
接口层:该层提供一系列接口让用户直接参与使用,包含信息配置与实际数据操做调用。配置方式包括:基于 XML 配置方式、基于 Java API 配置方式两种方式,用户也能够经过接口层 API 对数据库发起增删改查等操做的请求, 本层接口会把接收到的调用请求交给数据处理层的构件去处理。
数据处理层:该层是 Mybatis 的核心层,负责数据处理,主要包括SQL 参数映射解析、SQL 语句的实际执行、执行结果集的映射处理等。
咱们知道,Mybatis 框架让用户只须要提供配置信息,而且专一于 SQL 的编写便可,对于链接管理数据库/事务,或实际的 SQL 参数映射/语句执行/结果集映射等操做,做为用户都并不须要操心和参与。
可是,好奇的咱们其实想知道,Mybatis 核心部分的数据处理在总体流程中,是如何支撑用户请求?同时各个构件之间交互,又是怎样流转呢?
很巧,我这里有一张图,介绍了总体流程:
根据以上框架流程图进行讲解:
针对以上整体框架流程涉及到的这些硬核 API,下面咱们逐个展开介绍,但不会详细剖析源码与原理,包括构建细节,由于这些咱们在后续的源码剖析章节中都会详细分析。
对于 Mybatis 的全局配置对象 Configuration,我相信不管是初学者仍是资深玩家,都不会陌生。整个 Mybatis 的宇宙,都围绕着 Configuration 转。Configuration 对象的结构和 config.xml 配置文件的内容几乎相同,涵盖了properties (属性),settings (设置),typeAliases (类型别名),typeHandlers (类型处理器),objectFactory (对象工厂),mappers (映射器)等等,以前咱们有专门的一篇文章详细进行介绍,感兴趣的朋友往上翻到目录列表,找到 《Mybatis系列全解(四):全网最全!Mybatis配置文件XML全貌详解》 一文详细品味一番吧。
配置对象 Configuration 经过解析器 XMLConfigBuilder 进行解析,把全局配置文件 Config.xml 与 映射器配置文件 Mapper.xml 中的配置信息所有构建成完整的 Configuration 对象,后续咱们源码分析时详细剖析整个过程。
咱们知道,像 Configuration 和 Mapper 的配置信息存放在 XML 文件中,Mybatis 框架在构建配置对象时,必须先把 XML 文件信息加载成流,再作后续的解析封装,而 Resources 做为资源的辅助类,偏偏干的就是这个活,不管是经过加载本地资源或是加载远程资源,最终都会经过 类加载器 访问资源文件并输出文件流。
//加载核心配置文件 InputStream resourceAsStream = Resources.getResourceAsStream("Config.xml");
Resources 实实在在提供了一系列方法分分钟解决你的文件读取加载问题:
咱们一撞见 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 中灵活构建会话工厂的一系列接口:
会话工厂 SqlSessionFactory 是一个接口,做用是生产数据库会话对象 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,你能够指定:
SqlSessionFactory 一旦被建立就应该在 应用的运行期间 一直存在,没有任何理由丢弃它或从新建立另外一个实例。 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复建立屡次,屡次重建 SqlSessionFactory 被视为一种代码“坏习惯”。所以 SqlSessionFactory 的最佳做用域是 应用做用域。 最简单的就是使用单例模式或者静态单例模式。
请记住,建立 SqlSessionFactory ,一次就好!
每一个数据库对应一个 SqlSessionFactory 实例。SqlSessionFactory 一旦被建立, 它的生命周期应该与应用的生命周期相同 。因此,若是你想链接两个数据库,就须要建立两个 SqlSessionFactory 实例,每一个数据库对应一个;而若是是三个数据库,就须要三个实例,依此类推。
SqlSession 是一个接口,有两个实现类:
简单来讲,经过会话工厂构建出 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 ,并管理他们的生命周期,推荐搭配使用。
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() 来清除缓存。
Mybatis 在构建 Configuration 配置类时默认把 ExecutorType.SIMPLE 做为执行器类型,当咱们的会话工厂 DefaultSqlSessionFactory 开始生产 SqlSession 会话时,会同时构建执行器,此时就会依据配置类 Configuration 构建时指定的执行器类型来实例具体执行器 ,流程以下:
// 一、Configuration配置类构建时 // 指定了默认执行器类型为:普通执行器 protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE; // 二、Configuration配置类中 // 提供方法获取默认执行器类型 public ExecutorType getDefaultExecutorType() { return defaultExecutorType; } // 三、会话工厂建立 SqlSession 实例时 SqlSession session = sqlSessionFactory.openSession(); // 四、openSession 实际逻辑 public SqlSession openSession() { return openSessionFromDataSource( // 这里可就获取了默认执行器 configuration.getDefaultExecutorType(), null, false ); }
这里,确定有人想知道,咱们可否指定其它执行器呢?
答案是:固然能够,有两种方式指定:
// 建立 SqlSession 实例 SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.REUSE) // ExecutorType是一个枚举 // 有三个值SIMPLE, REUSE, BATCH
<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 ,最终进行数据库操做。
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 注入***(因此是系统默认也是咱们推荐的语句处理器)。
其实普通语句处理器、预执行语句处理器以及存储过程处理器,只是 Mybatis 对于 JDBC 的语句执行对象的简单包装而已,没有特别神秘,看如下 JDBC 的语句执行对象的类图关系也就可以清楚。
// 一、执行器构建语句处理器实例 public StatementHandler newStatementHandler(...) { // 构建路由语句处理器便可! StatementHandler statementHandler = new RoutingStatementHandler(...); // 其它逻辑忽略... return statementHandler; } // 二、实际构造方法(路由关系) 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 提供了基本接口,通常咱们不必自定义实现类,因此能够简单看一下便可:
ParameterHandler 是一个参数处理器接口,它负责把用户传递的参数转换成 JDBC Statement 所须要的参数,底层作数据转换的工做会交给类型转换器 TypeHandler,后面会介绍。
很显然,须要对传入的参数进行转换处理的 StatementHandler 实例只有两个,分别是:
上面在介绍语句处理器的时候,咱们有介绍说基础语句处理器 BaseStatementHandler 在进行实例构建时,会同时构建参数处理器与结果集处理器,因此参数处理器就是在此时被构建。
// 基础语句处理器 public abstract class BaseStatementHandler{ // 构造实例时 protected BaseStatementHandler(...){ // 其它逻辑可忽略... // 一、构建参数处理器 this.parameterHandler = conf.newParameterHandler(...); // 二、构建结果集处理器 this.resultSetHandler = conf.newResultSetHandler(...); } }
对于参数处理器接口,相对简单,只有1个默认实现类 DefaultParameterHandler ,该接口只有两个方法,分别是:
// 有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>
// 默认结果集处理器 public class DefaultResultSetHandler{ // 处理输出参数 public void handleOutputParameters(...) { // 获取参数 final Object parameterObject = parameterHandler.getParameterObject(); // 其它存储过程输出参数处理逻辑... } }
ResultSetHandler 是一个结果集处理器接口,它负责负责将 JDBC 返回的结果集 resultSet 对象转换为 List 类型的集合,是在语句处理器构建实例时被同时建立,底层作数据转换的工做会交给类型转换器 TypeHandler,它有1个默认实现类 DefaultResultSetHandler,该接口有3个方法,分别是:
结果集处理器对于 JDBC 返回的结果集的基本处理,是先获取咱们在映射器 Mapper 中指定 resultType 或 resultMap 映射关系,而后遍历解析结果集中的每一列数据,底层经过 MetaObject 对象作相关的反射处理。
对于详细的源码逻辑,咱们后续源码剖析部分详细讲。
不讲不是中国人 O(∩_∩)O ~
TypeHandler 是一个类型转换器/处理器接口,它负责 Java 数据类型和 JDBC 数据类型之间的映射与转换,当对 Statement 对象设置参数时,由 JavaType 转换为 JdbcType,当对 Statement 返回结果集进行封装映射时,又会将 JdbcType 转换为 JavaType。
通常,咱们能够直接使用 Mybatis 内置的类型处理器,简单看了一下有 65+ 个,固然咱们是能够根据业务须要自定义类型处理器的,以便处理复杂类型或非标类型。
具体作法为:
一、实现 org.apache.ibatis.type.TypeHandler
接口;
二、继承 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 的介绍。
MappedStatement 语句对象,就是咱们在映射器 Mapper 中维护的每一条语句,例如 <select|update|delete|insert>,Mybatis 中经过语句构造器 XMLStatementBuilder 对每个语句进行解析:
整个解析过程分为4步骤:
// Configuration 配置解析器 public class XMLConfigBuilder{ // 解析映射器 private void mapperElement(){ // 建立映射器解析实例 XMLMapperBuilder mapperParser = new XMLMapperBuilder(...); // 开始解析 mapperParser.parse(); } }
二、映射对象解析器 XMLMapperBuilder 解析语句
// 映射对象解析器 public class XMLMapperBuilder{ // 一、解析入口 public void parse() { // 解析映射器文件 configurationElement( parser.evalNode("/mapper")); } // 二、节点解析 private void configurationElement(XNode context) { // 构建语句对象 buildStatementFromContext( context.evalNodes( "select|insert|update|delete")); } // 三、最终调用语句解析器 private void buildStatementFromContext(){ // 建立语句解析实例 XMLStatementBuilder statementParser = new XMLStatementBuilder(); // 解析语句节点 statementParser.parseStatementNode(); } }
三、语句解析器 XMLStatementBuilder 解析每个节点
// 语句解析器 public class XMLStatementBuilder{ // 解析语句节点 public void parseStatementNode() { // 经过语句辅助类构建语句对象 builderAssistant.addMappedStatement(...) } }
四、语句辅助类 MapperBuilderAssistant 添加进语句集合中
// 语句辅助类 public class MapperBuilderAssistant{ // 添加语句对象 public MappedStatement addMappedStatement( // 最终添加到配置类的语句集合中 configuration.addMappedStatement(statement); } }
SqlSource 是一个 SQL 源接口,它会结合用户传递的参数对象 parameterObject,动态地生成 SQL 语句,并最终封装成 BoundSql 对象。SqlSource 接口有5个实现类,分别是:StaticSqlSource、DynamicSqlSource、RawSqlSource、ProviderSqlSource、VelocitySqlSource (这只是一个测试用例,而非真正模板 Sql 源实现类)。
SqlSource 实例在配置类 Configuration 解析阶段就被建立,Mybatis 框架会依据3个维度的信息来选择构建哪一种数据源实例:
SqlSource 接口只有一个方法 getBoundSql ,就是建立 BoundSql 对象。
public interface SqlSource { BoundSql getBoundSql(Object parameterObject); }
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 ~