上一篇博客的传送门:MyBatis 入门到精通(五)
Mybatis 是一个对数据库进行操作的 ORM 框架,主要是封装提供灵活的增删改查sql,开发中,service层能够通过mybatis组件查询和修改数据库中表的数据;作为查询工具,mybatis有使用缓存,我们今天来讲讲 MyBatis 的缓存操作。
缓存
在计算机里面,任何信息都有源头,缓存一般指源头信息读取后,放在内存或者其他读取较快的地方,下次读取相同信息不去源头查询而是直接从内存(或者能快速存取的硬件)读取。这样可以减少硬件使用,提高读取速度。
CPU 的缓存
在计算机系统中,CPU高速缓存(英语:CPU Cache,在本文中简称缓存)是用于减少处理器访问内存所需平均时间的部件。在金字塔式存储体系中它位于自顶向下的第二层,仅次于CPU寄存器。其容量远小于内存,但速度却可以接近处理器的频率。
当处理器发出内存访问请求时,会先查看缓存内是否有请求数据。如果存在(命中),则不经访问内存直接返回该数据;如果不存在(失效),则要先把内存中的相应数据载入缓存,再将其返回处理器。
缓存之所以有效,主要是因为程序运行时对内存的访问呈现局部性(Locality)特征。这种局部性既包括空间局部性(Spatial Locality),也包括时间局部性(Temporal Locality)。有效利用这种局部性,缓存可以达到极高的命中率。
在处理器看来,缓存是一个透明部件。因此,程序员通常无法直接干预对缓存的操作。但是,确实可以根据缓存的特点对程序代码实施特定优化,从而更好地利用缓存。
对比一下 CPU 的缓存,这样有助于我们学习 MyBatis 的缓存。
MyBatis 的缓存
mybatis 也是这样,查询数据库的数据之后,mybatis 可以把查询结果缓存到内存,下次查询如果查询语句相同,并且查询相关的表的数据没被修改过,就可以直接返回缓存中的结果,而不用去查询数据库的语句,有效节省了时间。
MyBatis 的缓存分类
- 一级缓存
- 二级缓存
一级缓存
基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空。一级缓存默认是长开启的。
效果演示
一级缓存是系统自带的缓存,不需要任何额外的操作。同一个 session ,多次对 7788 数据进行查询,第二次就不需要连接数据库。
代码部分:
SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(in);
SqlSession session = ssf.openSession();
EmpMapper mapper = session.getMapper(EmpMapper.class);
Emp emp1 = mapper.findEmpByNo(7788);
Emp emp2 = mapper.findEmpByNo(7788);
System.out.println(emp2);
System.out.println(emp1);
输出的日志:
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@1040be71]
==> Preparing: select * from emp where empState=1 and empNo=?
==> Parameters: 7788(Integer)
<== Columns: empno, ename, job, mgr, hiredate, sal, comm, deptno, empstate
<== Row: 7788, SCOTT, ANALYST, 7566, 1987-07-13, 3000.00, null, 20, 1
<== Total: 1
Emp(empNo=7788, ename=关为, job=程序猿, mgr=7566, hireDate=1987-07-13, sal=3000.0, comm=null, deptNo=20, empState=1, dept=null, mgrEmp=null)
Emp(empNo=7788, ename=关为, job=程序猿, mgr=7566, hireDate=1987-07-13, sal=3000.0, comm=null, deptNo=20, empState=1, dept=null, mgrEmp=null)
生命周期
MyBatis 在开启一个数据库会话时,会创建一个新的 SqlSession 对象,SqlSession 对象中会有一个新的 Executor 对象。Executor 对象中持有一个新的 PerpetualCache 对象。当会话结束时,SqlSession 对象及其内部的 Executor 对象还有 PerpetualCache 对象也一并释放掉。当发生下列情况时一级缓存的内容会失效:
- 如果 SqlSession 调用了 close()方法,会释放掉一级缓存 PerpetualCache 对象,一级缓存将不可用。
- 如果 SqlSession 调用了 clearCache(),会清空 PerpetualCache 对象中的数据,但是该对象仍可使用。
- SqlSession 中执行了任何一个 update 操作(update()、delete()、insert()) ,都会清空 PerpetualCache 对象的数据,但是该对象可以继续使用。
怎么判断某两次查询是完全相同的查询
mybatis 认为,对于两次查询,如果以下条件都完全一样,那么就认为它们是完全相同的两次查询。
- 传入的 statementId
- 查询时要求的结果集中的结果范围
- 这次查询所产生的最终要传递给 JDBC java.sql.Preparedstatement 的 Sql语句字符串(boundSql.getSql() )
- 传递给 java.sql.Statement 要设置的参数值
二级缓存
二 级 缓 存 与 一 级 缓 存 其 机 制 相 同 , 默 认 也 是 采 用 PerpetualCache , HashMap 存 储 , 不 同 在 于 其 存 储 作 用 域 为Mapper,也被称之为 namespace 级别缓存,并且可自定义存储源,如 Ehcache。
为什么需要二级缓存
之所以称之为“二级缓存”,是相对于“一级缓存”而言的。既然有了一级缓存,那么为什么要提供二级缓存呢?
我们知道,在一级缓存中,不同 session 进行相同 SQL 查询的时候,是查询两次数据库的。显然这是一种浪费,既然 SQL 查询相同,就没有必要再次查库了,直接利用缓存数据即可,这种思想就是 MyBatis 二级缓存的初衷。
另外,Spring 和 MyBatis 整合时,每次查询之后都要进行关闭 sqlsession ,关闭之后数据被清空。所以 MyBatis 和 Spring 整合之后,一级缓存是没有意义的。如果开启二级缓存,关闭 sqlsession 后,会把该 sqlsession 一级缓存中的数据添加到 mapper namespace 的二级缓存中。
这样,缓存在sqlsession 关闭之后依然存在。
开启二级缓存
Namespace 层面上的二级缓存默认是不开启的,二级缓存的开启需要进行配置。开启的步骤很简单,按照以下三部完成即可:
1. bean 包中的类需要序列化。
实现二级缓存的时候,MyBatis 要求返回的 POJO 必须是可序列化的。
package com.dailyblue.java.bean;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Emp implements Serializable {
......
}
2. 在 config.xml 中设置二级缓存开启。
<settings>
<setting name="cacheEnabled" value="true" />
</settings>
3. 对具体的 Mapper 单独允许二级缓存的使用。
要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
<cache/>
这个简单语句的效果如下:
- 映射语句文件中的所有 select 语句的结果将会被缓存。
- 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
- 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
- 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
- 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
- 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
注意:多个 session 使用一个缓存数据时,必须等待第一个 session 释放资源,其后 session 才能使用缓存数据。
cache 标签的相关属性
我们在刚才的案例中只是引入了缓存,我们还可以对缓存进行更加详细的配置:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
可用的 eviction 清除策略有:
- LRU – 最近最少使用:移除最长时间不被使用的对象。
- FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
- SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
- WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
默认的清除策略是 LRU。
其他相关属性:
- flushInterval(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
- size(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
- readOnly(只读)属性可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。
注意:二级缓存是事务性的。这意味着,当 SqlSession 完成并提交时,或是完成并回滚,但没有执行 flushCache=true 的 insert|delete|update 语句时,缓存会获得更新。
在下一篇博客中,我们讲解 MyBatis 注解的使用。---- MyBatis 入门到精通(七)