各种Executor的作用
在前面的文章中我们演示了通过SqlSession来执行sql,其实SqlSession只是一个api类,主要是为了方便用户的使用,典型的门面模式
Executor通过操作StatementHandler来执行sql,并返回结果,因此Executor起到了一个承上启下的作用
在mybatis中最重要的组件有如下4个
Executor:驱动sql执行
StatementHandler:调用Statement执行sql
ParameterHandler:给执行的sql设置参数
ResultSetHandler:将返回的ResultSet封装为Java对象
Executor的类的继承关系如下图所示
Executor的设计是一个典型的装饰者模式,SimpleExecutor,ReuseExecutor是具体实现类,而CachingExecutor是装饰器类
类型 | 作用 |
---|---|
SimpleExecutor | 默认配置,使用PreparedStatement对象访问数据库,每次访问都要创建新的PreparedStatement对象 |
ReuseExecutor | 使用PreparedStatement对象访问数据库,访问时会重用statement对象 |
BatchExecutor | 批量执行增删改sql |
BaseExecutor | 用来维护一级缓存 |
CachingExecutor | 用来维护二级缓存 |
其中BaseExecutor的设计又用到了模版模式,例如query方法封装了操作一级缓存的内容,而其实现类会重写doQuery方法,到数据库中去查询数据
后面我们会详细分析一级缓存和二级缓存,先演示一下三种具体Executor的区别
public class UserInfo {
private Integer id;
private String name;
private Integer age;
}
public interface UserInfoMapper {
@Select("select * from user_info WHERE id = #{id}")
UserInfo selectById(int id);
@Update("update user_info set name = #{name} where id = #{id}")
int updateNameById(String name, Integer id);
}
mybatis-config.xml
初始化环境测试环境
测试SimpleExecutor
从日志可以看到SimpleExecutor每次都会重新编译Statement
测试ReuseExecutor
从日志可以看到ReuseExecutor会重用Statement,并不会每次重新编译Statement
测试BatchExecutor
BatchExecutor是用来批量执行增删改sql的
当执行完上面的Test后你会发现更改并未生效,你需要手动刷行一下,如下所示
假如你执行了多个更改sql,只有当你手动执行的时候才会刷新
当一次执行的sql过多时,用SimpleExecutor可能会有性能问题,此时你就可以选用BatchExecutor来执行sql
执行流程
默认情况下我们都是使用SimpleExecutor来执行sql,以doQuery为例看一下具体的执行流程
org.apache.ibatis.executor.SimpleExecutor#doQuery
这段代码就包含了整体的执行流程
- 首先根据配置创建一个StatementHandler
- 然后利用ParameterHandler给Statement设置参数
- 最后StatementHandler调用Statement执行sql,并通过ResultSetHandler将ResultSet封装成需要的对象,返回给用户
mybatis中有三种具体的StatementHandler,每个类的作用如下
StatementHandler 实现类 | 作用 |
---|---|
SimpleStatementHandler | 调用Statement执行sql,因此要执行的sql没有?,不用设置参数 |
PreparedStatementHandler | 调用PreparedStatement执行sql,sql有可能有?,当有?的时候需要设置参数 |
CallableStatementHandler | 调用CallableStatement执行脚本 |
ParameterHandler设置参数
org.apache.ibatis.executor.SimpleExecutor#prepareStatement
org.apache.ibatis.executor.statement.PreparedStatementHandler#parameterize
org.apache.ibatis.scripting.defaults.DefaultParameterHandler#setParameters
这个是设置参数最重要的方法,后续会单开一节来分析
另外你可以看到SimpleStatementHandler#parameterize方法是一个空实现,因为SimpleStatementHandler通过调用Statement来执行sql,不需要设置参数哈
org.apache.ibatis.executor.statement.SimpleStatementHandler#parameterize
StatementHandler调用Statement执行sql
调用BoundSql#getSql获取的sql只会有?,并且在前面通过ParameterHandler设置好参数了,因此直接执行就行,然后通过ResultSetHandler将返回的结果封装成特定的类型返回
ResultSetHandler我就不分析了哈,mybatis源码最复杂的一个类,没有之一
DefaultSqlSession有问题?
之前的文章我们分析过,在启动的过程我们会利用SqlSessionFactoryBuilder创建SqlSessionFactory,最终返回的是DefaultSqlSessionFactory,调用openSession方法返回的是DefaultSqlSession。
但是DefaultSqlSession这个api类还并不是很好用,有很多问题
比如DefaultSqlSession不是线程安全的,所以DefaultSqlSession不能是单例,另外我们还得手动关闭DefaultSqlSession
为什么DefaultSqlSession不是线程安全的呢?
因为一个DefaultSqlSession只会开启一个Connection。所以当多个线程使用DefaultSqlSession时,其中一个线程执行完毕,调用close方法,将Connection关闭,另一个线程就会报错
当使用sqlSessionManager时,会利用ThreadLocal来保证线程安全
getMapper方法传入的SqlSession是自己
org.apache.ibatis.session.SqlSessionManager#getMapper
因此当mapper接口操作sql时会调用到SqlSessionManager中的方法
SqlSessionManager将执行过程交给sqlSessionProxy,sqlSessionProxy又是一个动态代理类
sql的执行过程会进入SqlSessionInterceptor#invoke方法,先从ThreadLocal中获取,如果没有的话再创建,保证了线程安全
所以使用SqlSessionManager比直接使用DefaultSqlSession有如下2个好处
- 利用try-with-resource语法糖,解决自动关闭
- 利用ThreadLocal,解决线程安全
参考博客
sqlsession好文
[2]https://zhuanlan.zhihu.com/p/100533979