【重难点】【MyBatis 01】MyBatis执行流程、MyBatis有哪些Executor、MyBatis插件原理、MyBatis缓存机制
文章目录
一、MyBatis执行流程
我们都知道 MyBatis 是对 JDBC 的简易封装,它的出现某种程度上是为了简化 JDBC 代码和参数的手工配置,以及结果集的封装问题。不管怎么样,JDBC 的那一套是不会变的,只是做了抽象、封装、归类等,所以想要理解 MyBatis 的执行流程,我们先回顾一下 JDBC 的执行流程
- 注册驱动
- 获取 Connection 连接
- 获取传输器
- 执行 SQL
- 处理结果集
- 关闭资源
private static void Connection(String url, String user, String password, String name, String pwd)
throws ClassNotFoundException, SQLException{
//1.注册驱动
class.forName("come.mysql.cj.jdbc.Driver");
//2.获取 Connection 连接
Connection connection = DriverMapper.getConnection(url, user, password);
//3.获取传输器
Statement statement = connection.createStatement();
//4.执行 SQL
String sql = "selection id from user where name = ? and password = ?";
PreparedStatement ps = connection.prepareStatement(sql);
ps.setString(1,name);
ps.setString(2,pwd);
ResultSet resultSet = ps.executeQuery();
//5.处理结果集
if(resultSet.next()){
System.out.println("登陆成功!");
}else{
System.out.println("用户名或密码错误!");
}
//6.关闭资源
resultSet.close();
ps.close();
connection.close();
}
MyBatis 的执行流程分为 8 个 步骤
1、读取 MyBatis 的核心配置文件,其中配置了数据库连接、属性、类型别名、类型处理器、插件、环境配置、映射器等信息,这个过程中有一个比较重要的部分就是配置映射文件。最终这个核心配置文件会被封装成一个 Configuration 对象
2、加载映射文件,即 SQL 映射文件,该文件中配置了操作数据库的 SQL 语句,映射文件在核心配置文件中加载。常见的配置方式有两种,一种是包扫描,另一种是 mapper 找到配置文件的位置
3、构造会话工厂获取 SqlSessionFactory
4、创建会话对象 SqlSession。由会话工厂创建 SqlSession 对象,对象中包含了执行 SQL 语句的所有方法,每个线程都应该有他自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的
5、Executor 执行器,是 MyBatis 的核心,负责 SQL 语句的生成和查询缓冲的维护,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句
6、MappedStatement 对象,它是对解析的 SQL 的封装,一个 MappedStatement 代表了一个 SQL 语句标签
7、输入参数映射。输入的参数类型可以是基本数据类型,也可以是 Map、List、POJO 等复杂数据类型。这个过程类似于 JDBC 的预编译处理参数的过程,有两个属性 parameterType 和 parameterMap
8、封装结果集。可以封装成多种类型,可以是基本类型,也可以是 Map、List、POJO 等复杂数据类型。封装结果集的过程和 JDBC 一样,有两个常用的属性 resultType 和 resultMap
public static void main(String[] args) throws Exception}
//1.加载配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
//2.创建 SqlSessionFactory 对象(实际创建的是 DefaultSqlSessionFactory 对象)
SqlSessionFactory builder = new SqlSessionFactoryBuilder().build(inputStream);
//3.创建 SqlSession 对象(实际创建的是 DefaultSqlSession 对象)
SqlSession sqlSession = builder.openSession();
//4.创建代理对象
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//5.执行查询语句
List<User> users = mapper.selectUserList();
//6.释放资源
sqlSession.close();
inputStream.close();
}
与 JDBC 的区别在于:
- 注册驱动和获取连接的部分都抽取到了核心配置文件中
- SQL 语句抽取到了映射文件中
总结一下 MyBatis 的执行流程:
- 加载解析核心配置文件和映射文件
- 处理参数
- 执行查询
- 封装结果集
二、MyBatis有哪些Executor
SimpleExecutor
每执行一次 update 或 select,就开启一个 Statement 对象,用完立刻关闭 Statement 对象
ReuseExecutor
执行 update 或 select 时,以 SQL 作为 key 查找 Statement 对象,存在就使用,不存在就创建,用完后,不会关闭 Statement 对象,而是放在 Map 内,供下一次使用
BatchExecutor
执行 update(不能执行 select,JDBC 批处理不支持 select),将所有 SQL 都添加到批处理中,等待统一执行,它缓存了多个 Statement 对象,每个 Statement 对象都是在 SQL 添加完毕后,等待注意执行批处理
CachingExecutor
CachingExecutor 是一个 Executor 的装饰器,它为 Executor 对象增加了二级缓存的相关功能,委托的执行器对象可以是 SimpleExecutor、ReuseExecutor、BatchExecutor 中的任意一个。执行 update 方法前会判断是否清空二级缓存,执行 query 方法前会先在二级缓存中查询,命中失败再通过被代理类查询
使用场景
Executor 的这些特点,都严格限制在 SqlSession 的生命周期内。MyBatis 的默认执行器是 SimpleExecutor,需要配置其它执行器,在创建 SqlSession 对象的时候指定执行器的类型即可
ReuseExecutor 和 BatchExecutor 适用于特定场景的 SQL 执行,且必须自己维护 Statement 对象
三、MyBatis插件
一般开源框架都会提供扩展点,让开发者自行扩展,增加框架的灵活性。基于插件机制可以实现很多有用的功能,比如分页、分表、监控等,这种通用的功能就如同 AOP 一样,横切在数据操作上。对于 MyBatis 插件,它可以对框架进行扩展,实现自定义功能,并且对于用户来说是无感知的
MyBatis 中只是针对四大组件提供了扩展机制,这四个组件分别是;
- Executor(SQL 执行器)
- StatementHandler(SQL 语法构建器)
- ParameterHandler(参数处理器)
- ResultSetHandler(结果集处理器)
MyBatis 插件的核心思想是使用 JDK 动态代理,对这四个对象进行增强。具体做法是:创建一个类实现 MyBatis 的拦截器接口,并且加入到拦截器链中,在创建核心对象的时候,不直接返回,而是遍历拦截器链,把每一个拦截器都作用于核心对象中,这样,MyBatis 创价的呢核心对象其实都是代理对象
MyBatis 中允许拦截的方法如下
- Executor(update,query,commit,rollback)
- StatementHandler(prepare,parameterize,batch,update,query 等)
- ParameterHandler(getParameterObject,setParameters 等)
- ResultSetHandler(handlerResultSets,handleOutputParameters 等)
四、MyBatis缓存机制
1.一级缓存
在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的 SQL,MyBatis 提供了一级缓存的方案优化这部分场景,如果是相同的 SQL 语句,会优先命中一级缓存,避免直接对数据库进行查询,提高了性能
在 SqlSession 中持有 Executor,每个 Executor 中有一个 LocalCache。当用户发起查询时,MyBatis 根据当前执行的语句生成 MappedStatement,在 Local Cache 进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入 Local Cache,最后返回结果给用户。具体实现类的类关系如下图所示
在 MyBatis 的配置文件中添加如下语句,就可以使用一级缓存
<setting name="localCacheScope" value="SESSION"/>
共有两个选项,SESSION 或者 STATEMENT,默认是 SESSION,即在一个 MyBatis 会话中执行的所有语句,都会共享这个一缓存。另一种是 STATEMENT,可以理解为缓存只对当前执行的这一个 Statement 有效
说明
- MyBatis 一级缓存的生命周期和 SqlSession 一致
- MyBatis 一级缓存内部设计简单,只是一个没有容量限定的 HashMap,在缓存的功能性上有所欠缺
- MyBatis 的一级缓存最大范围是 SqlSession 内部,有多个 SqlSession 或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为 Statement
2.二级缓存
一级缓存最大的共享范围是一个 SqlSession 内部,如果多个 SqlSession 之间需要共享缓存,则需要使用到二级缓存。开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在 CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示
二级缓存开启后,同一个 namespace 下的所有操作语句,都影响着同一个 Cache,即二级缓存被多个 SqlSession 共享,是一个全局的变量
要正确使用二级缓存,需要完成如下配置
1、在 MyBatis 的配置文件中开启二级缓存
<setting name="cacheEnabled" value="true"/>
2、在 MyBatis 的映射文件中配置 cache 或者 cache-ref
cache 标签用于声明这个 namespace 使用二级缓存,并提供多个参数进行自定义配置
<cache/>
cache-ref 标签代表引用别的命名空间的 Cache 配置,两个命名空间的操作使用的是同一个 Cache
<cache-ref namespace="mapper.StudentMapper"/>
说明
- MyBatis 的二级缓存相对于一级缓存来说,实现了 SqlSession 之间缓存数据的共享,同时粒度更加的细,能够达到 namespace 级别。Cache 接口有多种实现类,对 Cache 的可控性也更强
- MyBatis 在多表查询时,极大可能会出现脏数据,有设计上的缺陷,安全使用二级缓存的条件比较苛刻
- 在分布式环境下, 由于默认的 MyBatis Cache 实现都是基于本地的,分布式环境下必然会出现读取到脏数据,需要使用集中式缓存将 MyBatis 的 Cache 接口实现,有一定的开发成本,直接使用 Redis、Memcached 等分布式缓存可能成本更低,安全性也更高