SqlSession、Executor的介绍
1.SqlSession
-
SqlSession
是MyBatis的一个重要接口,它对外(使用者)提供了数据库操作和事务管理的功能。 -
SqlSession
的默认实现类是DefaultSqlSession
,内部维护了一个Executor对象,用于真正执行SQL语句和管理事务。 -
正常情况一个
SqlSession
对应一次数据库会话,数据库会话操作结束时可以执行关闭方法,销毁SqlSession对象。
2.Executor
1.负责对内(mybatis内部)执行SQL语句和管理事务。
2.Executor
接口有多个实现类,如SimpleExecutor
、ReuseExecutor
、BatchExecutor
和CachingExecutor
,分别对应sql不同的执行方式和缓存策略。
3.DefaultSqlSession
根据配置文件中的defaultExecutorType属性来选择使用哪种Executor
。
4.由SqlSession
进行管理,一个SqlSession
对象对应一个自己的Executor
对象。数据库会话结束时可以执行关闭方法,销毁Executor
对象。
3.SqlSession和Executor的联系跟区别
SqlSession
是对外(使用者)提供的接口,Executor
是内部代码的操作类。大部分情况下用户无法感知到Executor。
如下使用mybatis查询sql时,SqlSession
的api即可全部满足要求。插件等情况除外。
-
SqlSession
是一个数据库会话对象,它封装了数据库连接和Executor对象,每个SqlSession
都有一个自己的Executor
对象。 -
SqlSession
提供的一系列的方法都是通过调用Executor对象的相应方法来实现的,所以Executor
中的方法大多在SqlSession
中能找到相同的。
源码解析
以下按xml配置文件读取形式解析源码。
1.SqlSession跟Executor对象的创建流程
main方法代码如下,SqlSession
对象是通过SqlSessionFactory#openSession
创建的。
追踪debug到DefaultSqlSessionFactory#openSessionFromDataSource
。
1.通过事务工厂TransactionFactory#newTransaction
创建事务。
2.调用Configuration#newExecutor
,传入新创建的事务tx
,需要创建的Executor类型execType
,创建Executor对象。
3.调用DefaultSqlSession
构造方法创建SqlSession
对象。
先看DefaultSqlSession
的构造方法,只是单纯的属性赋值,没有其他操作,所以SqlSession
的对象创建过程到此结束了。
核心主要在Executor
对象的创建,我们继续看Configuration#newExecutor
1. 默认的Executor
类型为简单类型:SIMPLE
,我们最常用的也是该类型。
2. new了一个SimpleExecutor
对象
3. 如果开启了二级缓存,通过装饰器模式对Executor
对象进行包装。这部分逻辑将会在其他文章进行说明。
4. Executor
对象创建完成,调用插件的拦截方法,提供给用户对Executor
对象进行操作的切入点。
继续看SimpleExecutor构造方法
。调用了BaseExecutor的构造方法,这里没有做额外操作,只是属性的赋值。至此SqlSession跟Executor对象的创建便结束。
2.SqlSession方法的使用及解析
SqlSession
方法较多,主要分为以下几种类型
- 数据库数据操作 。如
selectList,update,delete
等。 - 事务管理 。如
commit,rollback
等。 - 数据库连接管理 。如
close
。 - 内部类管理 。如Mapper获取
getMapper
,缓存清理clearCache
等。
1.数据库数据操作
以selectList
为例进行讲解。
使用方式
1.第一个参数statement
,填写需要执行sql的类路径名+方法名称
2.第二个参数 parameter
,填写sql参数值。
其他重载的selectList
方法执行方式类似,不再放出示例。
对应的Mapper
步骤解析
通过debug定位到了DefaultSqlSession#selectList
1.获取查询的mapper方法对应的ms对象,MappedStatement
对象在mybatis启动时对sql进行了解析与封装,后续文章会单独进行说明。
2.使用executor#query
执行真正的查询逻辑
为了简便步骤,把二级缓存开关配置关闭了
ini
<setting name="cacheEnabled" value="false"/>
定位到BaseExecutor#query
,判断了一级缓存中是否存在对应key存在则直接读取缓存数据返回,不存在再查数据库,调用queryFromDatabase
debug到SimpleExecutor#doQuery
,看到查询方法使用StatementHandler#query
去执行。
1.通过Statement#execute
执行sql,Statement为jdbc提供的接口,包装了sql语句、sql参数、连接信息等。
2.通过ResultSetHandler#handleResultSets
转换sql结果。
以上便是SqlSession#selectList
,及其他数据库操作类型方法在mybatis中执行流程,总结如下
SqlSession
获取sql对应的处理器对象MappedStatement
,转交给Ececutor
Executor
处理一些sql外的步骤,如缓存获取等,sql的执行是调用jdbc接口去实现的,sql的内容存储在Statement
中,这个对象也是jdbc定义的。- 执行完sql后,
Executor
会使用ResultSetHandler
对结果进行转换,如转换为具体对象。
2.事务管理
以commit
为例进行讲解。
使用方式
执行完更改语句后,调用commit方法,由于当前数据库事务隔离级别是可重复读(REPEATABLE READ),所以在同一个事务中,多次查询结果是一样的,这里通过多个Session的方式验证未commit的数据。
可重复读级别下,update之后开启的事务,只能获取到已提交事务的数据改动的,未提交事务的数据改动是查询不到的,这里代码验证了commit成功提交了事务.
执行结果
步骤解析
看看调用SqlSession#commit
后会执行什么操作,通过debug定位到了DefaultSqlSession#commit
跟前面一样,还是通过Executor#commit
去执行真正的逻辑。
Executor#commit
步骤如下
- 清空一级缓存,一级缓存存放在
Executor
下的PerpetualCache
内。 - 清空
statements
对象,这个是Executor
类型为批量等清空才会用到,平时我们不用。 - 调用事务管理器,我们常用JDBC进行事务管理,让JDBC执行
commit
方法。
以上便是SqlSession#commit
,及其他事务相关方法在mybatis中执行流程,总结如下
SqlSession
调用Ececutor
的同名方法Executor
处理一些事务外的步骤,如缓存清空等,事务的管理是调用jdbc接口去实现的。
3.数据库连接管理
以close
为例进行讲解。
使用方式
需要执行的sql都执行完成后,就可以关闭会话归还资源了,如数据库连接资源等,执行close
方法即可。
步骤解析
老规矩,通过debug定位到了DefaultSqlSession#close
也是通过Executor#close
去执行真正的逻辑。
Executor#close
步骤如下
- 清空一级缓存,如果开启强制回滚还会将当前事务先回滚
- 调用事务管理器,我们常用JDBC进行事务管理,让JDBC事务管理器执行
close
方法关闭事务及连接。
先分析rollback
源码
- 清空一级缓存。
- 清空statement,我们平时用的Executor类型这个方法可以忽略,批量类型的才有作用。
- 如果开启强制回滚还会将当前事务先回滚。
再分析transaction#close
源码,这里以JDBC类型的事务管理器为例。核心方法在于Connection#close
,这里要注意,如果连接是非连接池类型,则close会直接关闭数据库连接,如果连接时连接池类型,则close会向连接池返还连接。
以下是连接池类型的Connection
对象,可以看到close会向数据库连接池返还连接
以上便是数据库连接管理方法SqlSession#close
在mybatis中执行流程,总结如下
SqlSession
调用Ececutor
的同名close
方法Executor
处理一些事务外的步骤,如缓存清空等,事务的管理是调用jdbc接口去实现的。- jdbc事务会被关闭,同时数据库连接根据连接是否池类型进行返还或者关闭。
4.内部类管理
以getMapper
为例进行讲解。
使用方式
getMapper
能根据Class类型获取到被mybatis加工后的代理Mapper对象,这个代理对象跟我们平时Spring中获取到的Mapper bean作用是相同的。调用代理对象的对应方法能执行到mapper xml文件中的sql。
步骤解析
这次不是通过Executor
去执行真正的逻辑,而是通过Configuration
先分析rollback
源码
- 清空一级缓存。
- 清空statement,我们平时用的Executor类型这个方法可以忽略,批量类型的才有作用。
- 如果开启强制回滚还会将当前事务先回滚。
继续debug,执行了MapperRegistry#getMapper
。
- 根据Mapper类从
knownMappers
中获取合适的MapperProxyFactory
(mapper对象工厂),knownMappers
是mybatis启动时便匹配完成的。 - 执行匹配到的
MapperProxyFactory
的newInstance
方法
核心方法在此,这里能看懂代理的逻辑,也就是为什么执行mapper代理对象的方法,会执行mapper xml文件同名方法的sql。
这里是通过jdk动态代理生成的代理对象,Proxy#newProxyInstance
是mybatis为了方便稍微做下封装。
既然是jdk动态代理,我们就看代理的InvocationHandler#invoke
方法,这里实现InvocationHandler
的是MapperProxy
MapperProxy#invoke
源码
- 如果代理对象执行的方法是源自Object的,如toString,hashCode等,则直接执行。 2.如果代理对象执行的方法不是源自Object的,则调用
MapperMethodInvoker#invoke
根据MapperMethodInvoker#invoke
一路debug到MapperMethod#execute
该方法总结如下:根据sql类型的不同,调用SqlSession
的对应操作方法,如insert调用insert,update调用update,select调用不同的select,我这里是调用了selectOne。
SqlSession#selectOne
的逻辑可以参考1.数据库数据操作 中SqlSession#select
逻辑的解析。到此源码解析已经完成,这也解释了为什么执行代理的Mapper对象,能执行到同名mapper xml文件中的同名方法的sql
最后总结
SqLSession
是mybatis对外提供操作的类,几乎所有mybatis相关操作,如 数据库数据操作、事务管理、数据库连接管理、内部类管理 都可以通过调用SqLSession
中的方法完成。
Executor
是mybatis对内操作的类,SqLSession
的 数据库数据操增删查改、事务管理 的主要步骤,基本都是调用Executor
来完成的。