目录

  • 前言
  • 环境版本
  • Mybatis的六剑客
    • SqlSession

      • 有何方法
      • 语句执行方法
      • 立即批量更新方法
      • 事务控制方法
      • 本地缓存方法
      • 获取映射方法
      • 有何实现类?
    • Executor
      • 实现类

        • BaseExecutor
        • CachingExecutor
        • SimpleExecutor
        • BatchExecutor
        • ReuseExecutor
      • SpringBoot中如何创建?
    • StatementHandler
      • 实现类

        • SimpleStatementHandler
        • PreparedStatementHandler
        • CallableStatementHandler
        • RoutingStatementHandler
    • ParameterHandler
    • TypeHandler
    • ResultSetHandler
  • 总结

前言

  • Mybatis的专题文章写到这里已经是第四篇了,前三篇讲了Mybatis的基本使用,相信只要认真看了的朋友,在实际开发中正常使用应该不是问题。没有看过的朋友,作者建议去看一看,三篇文章分别是Mybatis入门之基本操作Mybatis结果映射,你射准了吗?Mybatis动态SQL,你真的会了吗?
  • 当然,任何一个技术都不能浅藏辄止,今天作者就带大家深入底层源码看一看Mybatis的基础架构。此篇文章只是源码的入门篇,讲一些Mybatis中重要的组件,作者称之为六剑客

环境版本

  • 本篇文章讲的一切内容都是基于Mybatis3.5SpringBoot-2.3.3.RELEASE

Myabtis的六剑客

  • 其实Mybatis的底层源码和Spring比起来还是非常容易读懂的,作者将其中六个重要的接口抽离出来称之为Mybatis的六剑客,分别是SqlSessionExecutorStatementHandlerParameterHandlerResultSetHandlerTypeHandler
  • 六剑客在Mybatis中分别承担着什么角色?下面将会逐一介绍。
  • 介绍六剑客之前,先来一张六剑客执行的流程图,如下:

SqlSession

  • SqlSession是Myabtis中的核心API,主要用来执行命令,获取映射,管理事务。它包含了所有执行语句、提交或回滚事务以及获取映射器实例的方法。

有何方法

  • 其中定义了将近20个方法,其中涉及的到语句执行,事务提交回滚等方法。下面对于这些方法进行分类总结。

语句执行方法

  • 这些方法被用来执行定义在 SQL 映射 XML 文件中的 SELECT、INSERT、UPDATE 和 DELETE 语句。你可以通过名字快速了解它们的作用,每一方法都接受语句的 ID 以及参数对象,参数可以是原始类型(支持自动装箱或包装类)、JavaBean、POJO 或 Map。
<T> T selectOne(String statement, Object parameter)
<E> List<E> selectList(String statement, Object parameter)
<T> Cursor<T> selectCursor(String statement, Object parameter)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
int insert(String statement, Object parameter)
int update(String statement, Object parameter)
int delete(String statement, Object parameter)
  • 其中的最容易误解的就是selectOneselectList,从方法名称就很容易知道区别,一个是查询单个,一个是查询多个。如果你对自己的SQL无法确定返回一个还是多个结果的时候,建议使用selectList
  • insertupdatedelete方法返值是受影响的行数。
  • select还有几个重用的方法,用于限制返回行数,在Mysql中对应的就是limit,如下:
<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
void select (String statement, Object parameter, ResultHandler<T> handler)
void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
  • 其中的RowBounds参数中保存了限制的行数,起始行数。

立即批量更新方法

  • 当你将 ExecutorType 设置为 ExecutorType.BATCH 时,可以使用这个方法清除(执行)缓存在 JDBC 驱动类中的批量更新语句。
List<BatchResult> flushStatements()

事务控制方法

  • 有四个方法用来控制事务作用域。当然,如果你已经设置了自动提交或你使用了外部事务管理器,这些方法就没什么作用了。然而,如果你正在使用由 Connection 实例控制的 JDBC 事务管理器,那么这四个方法就会派上用场:
void commit()
void commit(boolean force)
void rollback()
void rollback(boolean force)
  • 默认情况下 MyBatis 不会自动提交事务,除非它侦测到调用了插入、更新或删除方法改变了数据库。如果你没有使用这些方法提交修改,那么你可以在commitrollback 方法参数中传入 true 值,来保证事务被正常提交(注意,在自动提交模式或者使用了外部事务管理器的情况下,设置 force 值对 session 无效)。大部分情况下你无需调用 rollback(),因为 MyBatis 会在你没有调用 commit 时替你完成回滚操作。不过,当你要在一个可能多次提交或回滚的 session 中详细控制事务,回滚操作就派上用场了。

本地缓存方法

  • Mybatis 使用到了两种缓存:本地缓存(local cache)和二级缓存(second level cache)。
  • 默认情况下,本地缓存数据的生命周期等同于整个 session 的周期。由于缓存会被用来解决循环引用问题和加快重复嵌套查询的速度,所以无法将其完全禁用。但是你可以通过设置 localCacheScope=STATEMENT 来只在语句执行时使用缓存。
  • 可以调用以下方法清除本地缓存。
void clearCache()

获取映射器

  • 在SqlSession中你也可以获取自己的映射器,直接使用下面的方法,如下:
<T> T getMapper(Class<T> type)
  • 比如你需要获取一个UserMapper,如下:
UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);

有何实现类

  • 在Mybatis中有三个实现类,分别是DefaultSqlSessionSqlSessionManagerSqlSessionTemplate,其中重要的就是DefaultSqlSession,这个后面讲到Mybatis执行源码的时候会一一分析。
  • 在与SpringBoot整合时,Mybatis的启动器配置类会默认注入一个SqlSessionTemplate,源码如下:
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)
//根据执行器的类型创建不同的执行器,默认CachingExecutor
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}

Executor

  • Mybatis的执行器,是Mybatis的调度核心,负责SQL语句的生成和缓存的维护,SqlSession中的crud方法实际上都是调用执行器中的对应方法执行。
  • 继承结构如下图:

实现类

  • 下面我们来看看都有哪些实现类,分别有什么作用。

BaseExecutor

  • 这是一个抽象类,采用模板方法的模式,有意思的是这个老弟模仿Spring的方式,真正的执行的方法都是doxxx()
  • 其中有一个方法值得注意,查询的时候走的一级缓存,因此这里注意下,既然这是个模板类,那么Mybatis执行select的时候默认都会走一级缓存。代码如下:
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
//此处的localCache即是一级缓存,是一个Map的结构
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
//执行真正的查询
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

CachingExecutor

  • 这个比较有名了,二级缓存的维护类,与SpringBoot整合默认创建的就是这个家伙。下面来看一下如何走的二级缓存,源码如下:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//查看当前Sql是否使用了二级缓存
Cache cache = ms.getCache();
//使用缓存了,直接从缓存中取
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
//从缓存中取数据
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
//没取到数据,则执行SQL从数据库查询
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//查到了,放入缓存中
tcm.putObject(cache, key, list); // issue #578 and #116
}
//直接返回
return list;
}
}
//没使用二级缓存,直接执行SQL从数据库查询
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
  • 这玩意就是走个二级缓存,其他没什么。

SimpleExecutor

  • 这个类像个直男,最简单的一个执行器,就是根据对应的SQL执行,不会做一些额外的操作。

BatchExecutor

  • 通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。

ReuseExecutor

  •  可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。
  • 内部的实现是通过一个HashMap来维护Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。

SpringBoot中如何创建

  • 在SpringBoot到底创建的是哪个执行器呢?其实只要阅读一下源码可以很清楚的知道,答案就在org.apache.ibatis.session.Configuration类中,其中创建执行器的源码如下:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//没有指定执行器的类型,创建默认的,即是SimpleExecutor
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
//类型是BATCH,创建BatchExecutor
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
//类型为REUSE,创建ReuseExecutor
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//除了上面两种,创建的都是SimpleExecutor
executor = new SimpleExecutor(this, transaction);
}
//如果全局配置了二级缓存,则创建CachingExecutor,SpringBoot中这个参数默认是true,可以自己设置为false
if (cacheEnabled) {
//创建CachingExecutor
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
  • 显而易见,SpringBoot中默认创建的是CachingExecutor,因为默认的cacheEnabled的值为true

StatementHandler

  • 熟悉JDBC的朋友应该都能猜到这个接口是干嘛的,很显然,这个是对SQL语句进行处理和参数赋值的。

实现类

  • 该接口也是有很多的实现类,如下图:

SimpleStatementHandler

  • 这个很简单了,就是对应我们JDBC中常用的Statement接口,用于简单SQL的处理

PreparedStatementHandler

  • 这个对应JDBC中的PreparedStatement,预编译SQL的接口。

CallableStatementHandler

  • 这个对应JDBC中CallableStatement,用于执行存储过程相关的接口。

RoutingStatementHandler

  • 这个接口是以上三个接口的路由,没有实际操作,只是负责上面三个StatementHandler的创建及调用。

ParameterHandler

  • ParameterHandler在Mybatis中负责将sql中的占位符替换为真正的参数,它是一个接口,有且只有一个实现类DefaultParameterHandler
  • setParameters是处理参数最核心的方法。这里不再详细的讲,后面会讲到。

TypeHandler

  • 这位大神应该都听说过,也都自定义过吧,简单的说就是在预编译设置参数和取出结果的时候将Java类型和JDBC的类型进行相应的转换。当然,Mybatis内置了很多默认的类型处理器,基本够用,除非有特殊的定制,我们才会去自定义,比如需要将Java对象以JSON字符串的形式存入数据库,此时就可以自定义一个类型处理器。
  • 很简单的东西,此处就不再详细的讲了,后面会单独出一篇如何自定义类型处理器的文章。

ResultSetHandler

  • 结果处理器,负责将JDBC返回的ResultSet结果集对象转换成List类型的集合或者Cursor
  • 具体实现类就是DefaultResultSetHandler,其实现的步骤就是将Statement执行后的结果集,按照Mapper文件中配置的ResultType或ResultMap来封装成对应的对象,最后将封装的对象返回。
  • 源码及其复杂,尤其是其中对嵌套查询的解析,这里只做个了解,后续会专门写一篇文章介绍。

总结

  • 至此,Mybatis源码第一篇就已经讲完了,本篇文章对Mybatis中的重要组件做了初步的了解,为后面更深入的源码阅读做了铺垫,如果觉得作者写的不错,在看分享一波,谢谢支持。

Mybati源码解析篇之六剑客!!!的更多相关文章

  1. 美团动态线程池实践思路开源项目(DynamicTp),线程池源码解析及通知告警篇

    大家好,这篇文章我们来聊下动态线程池开源项目(DynamicTp)的通知告警模块.目前项目提供以下通知告警功能,每一个通知项都可以独立配置是否开启.告警阈值.告警间隔时间.平台等,具体代码请看core ...

  2. IPerf——网络测试工具介绍与源码解析(1)

    IPerf是一个开源的测试网络宽带并能统计并报告延迟抖动.数据包丢失率信息的控制台命令程序,通过参数选项可以方便地看出,通过设置不同的选项值对网络带宽的影响,对于学习网络编程还是有一定的借鉴意义,至少 ...

  3. Spring系列(五):Spring AOP源码解析

    一.@EnableAspectJAutoProxy注解 在主配置类中添加@EnableAspectJAutoProxy注解,开启aop支持,那么@EnableAspectJAutoProxy到底做了什 ...

  4. 多线程与高并发(五)—— 源码解析 ReentrantLock

    一.前言 ReentrantLock 是基于 AQS 实现的同步框架,关于 AQS 的源码在 这篇文章 已经讲解过,ReentrantLock 的主要实现都依赖AQS,因此在阅读本文前应该先了解 AQ ...

  5. jQuery2.x源码解析(缓存篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 缓存是jQuery中的又一核心设计,jQuery ...

  6. jQuery2.x源码解析(构建篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 笔者阅读了园友艾伦 Aaron的系列博客< ...

  7. jQuery2.x源码解析(设计篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 这一篇笔者主要以设计的角度探索jQuery的源代 ...

  8. jQuery2.x源码解析(回调篇)

    jQuery2.x源码解析(构建篇) jQuery2.x源码解析(设计篇) jQuery2.x源码解析(回调篇) jQuery2.x源码解析(缓存篇) 通过艾伦的博客,我们能看出,jQuery的pro ...

  9. ExcelReport第二篇:ExcelReport源码解析

    导航 目   录:基于NPOI的报表引擎——ExcelReport 上一篇:使用ExcelReport导出Excel 下一篇:扩展元素格式化器 概述 针对上一篇随笔收到的反馈,在展开对ExcelRep ...

随机推荐

  1. 远程服务器的管理工具SSH

    1.SSH是什么? SSH:Secure Shell 安全外壳协议 建立在应用层基础上的安全协议 可靠,专为远程登录会话和其他网络服务提供安全性的协议 有效防止远程管理过程中的信息泄露问题 SSH客户 ...

  2. Java openrasp学习记录(二)

    Author:tr1ple 主要分析以下四个部分: 1.openrasp agent 这里主要进行插桩的定义,其pom.xml中定义了能够当类重新load时重定义以及重新转换 这里定义了两种插桩方式对 ...

  3. Cenos系统防火墙开放指定端口

    1.查看已经开放的端口 firewall-cmd --list-ports 2.开启指定端口 firewall-cmd --zone=public --add-port=2181/tcp --perm ...

  4. CSS学习第三天

    定位布局: 相对定位:相对于自身的位置进行偏移position需要搭配left right top bottom      position: relative; 绝对定位:相对于有position属 ...

  5. Nginx定时日志切割

    Nginx定时日志切割 现有的日志都会存在access.log文件中,但是随着时间的推移,这个文件的内容会越来越多,体积会越来越大,不便于运维 人员查看,所以我们可以通过把这个大的日志文件切割为多份不 ...

  6. C#LeetCode刷题-链表

    链表篇 # 题名 刷题 通过率 难度 2 两数相加   29.0% 中等 19 删除链表的倒数第N个节点   29.4% 中等 21 合并两个有序链表 C#LeetCode刷题之#21-合并两个有序链 ...

  7. Css 设置超过再两行显示省略号

    大部分场景都是超过一行就显示... <template> <div class="other-product-item item-name" :title=&qu ...

  8. 极简 Node.js 入门 - 2.2 事件

    极简 Node.js 入门系列教程:https://www.yuque.com/sunluyong/node 本文更佳阅读体验:https://www.yuque.com/sunluyong/node ...

  9. BLE GAP 协议和 GATT 协议

    BLE GAP 协议和 GATT 协议 最近要打算学习 Blufi 协议进行蓝牙配置,其中必然使用 GAP 协议和 GATT 协议,于是进行重新学习一番. BLE 是一个 Bluetooth SIG ...

  10. Oracle创建主键优劣

    创建主键方式 一个表的主键是唯一标识,不能有重复,不允许为空. 一个表的主键可以由一个字段或多个字段共同组成. -- 列级,表级建立主键 1.create table constraint_test ...