1、JDBC执行过程回顾
1.1原生JDBC操作说明
- 获取数据库驱动的方式
第一种是:直接注册数据库驱动DriverManager.registerDriver(new Driver());
第二种是:利用反射机制间接加载数据库驱劝,推荐用第二种Class.forName("com.mysql.jdbc.Driver");
- 取得数据库连接对象Connection
Connection conn = DriverManager.getConnection(数据库连接URL,用户名,密码);
- 执行SQL
insert/update/delete:PreparedStatement .executeUpdate(sql)
返回值表示影响记录的行数
select:PreparedStatement .exeucteQuery();
返回值表示符合条件的记录
1.2 着重介绍一下预编译器 Statement 介绍
通过该组件来发送对应的SQL与参数
- 简单 Statement
- 预处理 Statement
- 存储过程 Statement
后者继承自前者,也就是说简单执行器的所有功能,预处理执行器和存储过程执行器都有
1.3 如图所示
这里把Statement叫做执行器,只是一种说法,有些文章里也会叫做SQL处理器,但实质是一个东西
1.4 Statement 中非常规方法
-
addBatch(): 批处理操作,将多个SQL合并在一起,最后调用executeBatch 一起发送至数据库执行
-
setFetchSize():设置从数据库每次读取的数量单位。该举措是为了防止一次性从数据库加载数据过多,导致内存溢出。
2.MyBatis执行过程
2.1各个组件的作用
- 接口代理: 其目的是简化对MyBatis使用,底层使用动态代理实现。
- Sql会话: 提供增删改查API,其本身不作任何业务逻辑的处理,所有处理都交给执行器。这是一个典型的门面模式设计。
- 执行器: 核心作用是处理SQL请求、事物管理、维护缓存以及批处理等。执行器在的角色更像是一个管理员,接收SQL请求,然后根据缓存、批处理等逻辑来决定如何执行这个SQL请求。并交给JDBC处理器执行具体SQL。
- JDBC处理器:他的作用就是用于通过JDBC具体处理SQL和参数的。在会话中每调用一次CRUD,JDBC处理器就会生成一个实例与之对应(命中缓存除外)。
请注意在一次SQL会话过程当中四个组件的实例比值分别是 1:1:1:n 。
各个组件关系可以通过下面这张图了解。一个SQL请求通过会话到达执行器,然后交给对应的JDBC处理器进行处理。另外所有的组件都不是线程安全的,不能跨线程使用
3.Executor 执行器组件
Executor是MyBatis执行者接口,执行器的功能包括:
- 基本功能:改、查,没有增删的原因是,所有的增删操作都可以归结到改。
- 缓存维护:这里的缓存主要是为一级缓存服务,功能包括创建缓存Key、清理缓存、判断缓存是否存在。
- 事物管理:提交、回滚、关闭、批处理刷新。
对于这个接口MyBatis是有三个实现子类。分别是:
SimpleExecutor:简单执行器
ReuseExecutor:重用执行器
-
BatchExecutor:批处理执行器
3.1 简单执行器 SimpleExecutor
SimpleExecutor
是默认执行器,它的行为是每处理一次会话当中的SQl请求都会通过对应的StatementHandler 构建一个新个Statement,这就会导致即使是相同SQL语句
也无法重用Statement
,所以就有了(ReuseExecutor
)可重用执行器
3.2 可重用执行器 ReuseExecutor
ReuseExecutor
区别在于他会将在会话期间内的Statement进行缓存
,并使用SQL语句作为Key。所以当执行下一请求的时候,不在重复构建Statement
,而是从缓存中取出并设置参数,然后执行。
这也说明为啥执行器不能跨线程调用,这会导致两个线程给同一个Statement 设置不同场景参数。
3.3 批处理执行器 BatchExecutor
BatchExecutor
顾名思议,它就是用来作批处理的。但会将所 有SQL请求集中起来,最后调用Executor.flushStatements()
方法时一次性
将所有请求发送至数据库.
这里它是利用了Statement中的addBath 机制吗?不一定,因为只有连续相同的SQL语句并且相同的SQL映射声明,才会重用Statement,并利用其批处理功能。否则会构建一个新的Satement然后在flushStatements() 时一次执行。这么做的原因是它要保证执行顺序。跟调用顺序一至。
假设上图中相同的线条颜色,就是相同的SQL语句。为了保证执行顺序只有绿色线条合并成一个Statement而两条黄线不能,否则就会导致,后面的黄线先于中间的绿线执行,有违调用顺序
前面我们所说Executor其中有一个职责是负责缓存维护,以及事物管理。这三执行器并没有涉及,这部分逻辑去哪了呢?别急,缓存和事物无论采用哪种执行器,都会涉及,这属于公共逻辑。所以就完全有必要三个类之上抽象出一个基础执行器用来处理公共逻辑。
3.4 基础执行器 BaseExecutor
BaseExecutor
基础执行器主要是用于维护缓存和事物
。事物是通过会话中调用commit、rollback进行管理
。重点在于缓存这块它是如何处理的? (这里的缓存是指一级缓存
),它实现了Executor中的Query与update方法。会话中SQL请求,正是调用的这两个方法。Query方法中处理一级缓存逻辑,即根据SQL及参数判断缓存中是否存在数据,有就走缓存。否则就会调用子类的doQuery() 方法去查询数据库,然后在设置缓存。在doUpdate() 中主要是用于清空缓存。
3.5 缓存执行器 CachingExecutor
Executor 的子类还有一个CachingExecutor
,这是用于处理二级缓存
的。为什么不把它和一级缓存一起处理呢?因为二级缓存和一级缓存相对独立的逻辑,而且二级缓存可以通过参数控制关闭,而一级缓存是不可以的。综上原因把二级缓存单独抽出来处理。抽取的方式采用了装饰者设计模式,即在CachingExecutor 对原有的执行器进行包装,处理完二级缓存逻辑之后,把SQL执行相关的逻辑交给实至的Executor处理。
4.执行器总结
执行器的种类有:基础执行器
、简单执行器
、重用执行器
、批处理执行器
,此外通过装饰器形式添加了一个缓存执行器
。对应功能包括缓存处理
、事物处理
、重用处理
、批处理
,这些是多个SQL执行中有共性地方。执行器存在的意义就是去处理这些共性。 如果说每个SQL调用是独立的,不需要缓存,不需要事物也不需集中在一起进行批处理的话,Executor也就没有存在的必要。但事实上这些都是MyBatis中不可或缺的特性。所以才设计出Executor这个组件。
参考源码阅读网鲁班老师课《MyBatis源码解析大合集》内容详情参见
https://www.bilibili.com/video/BV1Tp4y1X7FM?from=search&seid=9615281135221050046&spm_id_from=333.337.0.0