MyBatis提供的缓存机制
缓存就是数据交换的缓冲区(称作:Cache),当某一硬件要读取数据时,会首先从缓存汇总查询数据,有则直接执行,不存在时从内存中获取。由于缓存的数据比内存快的多,所以缓存的作用就是帮助硬件更快的运行。缓存提供了一种存储方式,主要是用于存储数据,当客户端访问数据时可以直接访问缓存,从而减轻数据库的压力,提高数据库的性能。
数据的访问流程:
其中服务器主要的功能是和数据库进行数据交互,其主要操作无非是增删改查(查询操作比较多,特点是:频率高,不会改变数据),查询操作时服务器会从数据库获取数据的结果集,并不会引起数据本身的改变,而增、删、改的操作则会改变数据库中的数据。
服务端与数据库的交互是需要进行网络交互的。缓存则可以减少服务端和数据库的交互,从而减轻数据库的压力,提高数据库的性能,缓存主要是用于查询缓存。
缓存可以将数据库中的数据暂存,从而便于服务器的使用。不过,在使用缓存时需要注意脏数据
的处理。
举例:
当第一次查询 id = 1时,会将查询到用户信息放入到缓存,这样后续如果对 id = 1 的用户进行查询就可以直接查询缓存数据。
但如果当 id = 1 的数据已经存储在缓存中后,对 id = 1 的数据进行变更(例如对用户 id = 1 的电话做了变更),从而会导致数据库中数据的改变。
当数据进行变更后,此时如果查询 id = 1 的用户,使用了缓存进行查询,缓存中就存在的是更新前的旧数据。
此时查询到的数据就称为脏数据。
缓存更新的时机:
为了解决上述的脏数据问题,就需要对缓存进行更新,那么它更新的时机是什么呢?
- 当进行查询操作时:首先访问缓存,当缓存中没有对应的数据时,会在数据库查询数据,并将数据库中查询到的数据插入到缓存中,这样后续的查询就可以直接访问缓存。
- 当进行变更操作时(修改,删除等),会将数据变更到数据库中,此时会将对应的缓存信息清空。
- 等待再次查询时,通过访问缓存发现没有数据,就会再次查询数据库并将数据库返回的结果放入缓存。
- 通过该更新机制就能够保证缓存中的数据是最新的数据。
缓存的实现方式:
- 1、在单实例服务器下,可以在当前服务代码中通过添加HashMap集合实例来充当缓存。
- 不过需要注意的是:在集群服务器中,本地缓存(HashMap)无法将缓存数据共享给其他的服务器。
- 2、在多服务器下,需要通过缓存共享的形式来缓存数据,此时就会使用缓存服务器进行处理,常见的缓存服务器代表有:Redis,memCache等。
MyBatis缓存
使用缓存可以时应用更快地获取数据,避免频繁地数据库交互,尤其是在查询越多、缓存命中率越高地情况下,使用缓存地作用就越明显。MyBatis作为持久化框架,提供了非常强大地查询缓存特性,可以非常方便地配置和定制使用。
MyBatis提供了一级缓存、二级缓存两种缓存方式,一般提到MyBatis缓存的时候,都是指二级缓存。一级缓存(也叫本地缓存)默认会开启,并且不能控制,因此很少会提到。
- 一级缓存主要是sqlSession级别的缓存,在操作数据库时是需要构造sqlSession会话对象的,对同一个对象中的数据可以使用到缓存,不同的sqlSession之间的缓存是不同享的。
- 二级缓存主要是Mapper级别的缓存(namespace),多个sqlSession是共享缓存的。
一级缓存
一级缓存是sqlSession级别的缓存,作用于在同一个sqlSession当中,在同一个sqlSession中连续执行同一个SQL语句,第一次执行的结果会放入缓存当中,第二次进行查询操作时就可以直接访问缓存结果。
- MyBatis是默认支持一级缓存的,不需要做相关配置。
- 下面对一级缓存进行测试。测试场景如下:
- case1:同一个sqlSession下连续的执行同一个查询操作;
- case2:在同一个sqlSession下,先进行查询操作,再进行变更操作,然后接着进行同一个SQL查询;
- case3:不同的sqlsession下,进行同一个SQL查询操作;
- 测试环境与前面博客中的测试环境相同。
case1验证:
- 测试代码:
//case1:同一个sqlSession下连续的执行同一个查询操作
@Test
public void getStudentByID() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过接口产生代理类
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//第一次执行查询操作,查询id =1
Student student = mapper.getStudentByID(1);
System.out.println(student);
//第二次查询
Student student1 = mapper.getStudentByID(1);
System.out.println(student1);
}
- 日志打印:
- 结果:在同一个sqlSession下,连续的查询同一个SQL语句,只查询一次数据库,第二次查询即访问缓存拿到结果。
case2验证:
- 测试代码:
//case2:在同一个sqlSession下,先进行查询,在变更操作,然后进行同一个SQL查询
@Test
public void getStudentByID() {
SqlSession sqlSession = sqlSessionFactory.openSession();
//通过接口产生代理类
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
//第一次执行查询操作,查询id =1
Student student = mapper.getStudentByID(1);
System.out.println(student);
//进行更新SID=1的学生信息
Student student2 = new Student();
student2.setSID(1);
student2.setname("abc");
mapper.updateStudent(student2);
sqlSession.commit();
//第二次查询
Student student1 = mapper.getStudentByID(1);
System.out.println(student1);
}
- 日志打印:
- 结果:在第一次查询之后,会将数据存入缓存中,当进行了变更操作时,将缓存清空,第二次执行同一个SQL的查询操作时,此时缓中不存在数据,就会继续查询数据库。
case3验证:
- 测试代码:
//case3:不同的sqlsession下,同一个SQL查询操作
@Test
public void getStudentByID() {
//创建SQLSession实例1
SqlSession sqlSession1 = sqlSessionFactory.openSession();
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
//创建SQLSession实例2
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
//第一次执行查询操作
Student student = mapper1.getStudentByID(1);
System.out.println(student);
//第二次查询
Student student1 = mapper2.getStudentByID(1);
System.out.println(student1);
}
- 日志打印:
- 结果:在同一个sqlsession实例下,连续查询操作可以使用缓存,在不同的sqlSession实例下,缓存是不共享的。
二级缓存
二级缓存是Mapper级别的缓存,默认情况下二级缓存是关闭的。同一个Mapper下不同的sqlSession是可以共享二级缓存的,不同Mapper的缓存是相互隔离的。
二级缓存和一级缓存的区别:
- 二级缓存范围更大,多个sqlSession可以共享一个Mapper的二级缓存区域。
二级缓存使用步骤:
- 1、需要在全局配置文件中打开二级缓存的开关。
<settings>
<!--开启二级缓存的开关-->
<setting name="cacheEnabled" value="true"/>
</settings>
- 2、将映射的pojo类实现序列化。
public class Student implements Serializable
- 3、在mapper配置文件中添加cache标签。
<mapper namespace="com.tulun.dao.StudentMapper">
<!--开启本Mapper的二级缓存-->
<cache></cache>
</mapper>
- cache的参数说明:
<mapper namespace="com.tulun.dao.StudentMapper">
<!--
cache参数说明:
flushInterval(缓存刷新间隔)单位毫秒,默认情况下不设置,在变更操作时,进行缓存刷新
size:(引用次数)记录缓存对象的数量 默认值1024
readOnly(只读)参数为true:false 表示缓存的数据对象实例是不能被修改
eviction:缓存失效的策略 默认LRU
LRU:最近最少使用的:移除最长时间不使用的
FIFO:先进先出,按照缓存对象的顺序来淘汰
SOFT:软引用
WEAK:弱引用
-->
<cache eviction="LRU" readOnly="true" flushInterval="10000" size="12"></cache>
</mapper>
- cache的配置禁用:
- 在statement上配置useCache=“false”,禁用的是二级缓存。
<select id="getStudentByID" useCache="false" resultType="com.tulun.pojo.Student">
select * from Student where SID = #{sid}
</select>
二级缓存的测试:
-
case:在同一个Mapper实例下,不同的sqlSession执行同一个查询操作。
-
测试代码:
//case:在同一个Mapper实例下,不同的sqlsession下,同一个SQL查询操作
@Test
public void getStudentByID() {
//创建SQLSession实例1
SqlSession sqlSession1 = sqlSessionFactory.openSession();
StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
//创建SQLSession实例2
SqlSession sqlSession2 = sqlSessionFactory.openSession();
StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
//第一次执行查询操作
Student student = mapper1.getStudentByID(1);
//执行关闭操作,将SQLSession中的数据写入到二级缓存中
sqlSession1.close();
System.out.println(student);
//第二次查询
Student student1 = mapper2.getStudentByID(1);
System.out.println(student1);
}
- 日志打印:
- 结果:在同一个Mapper下,不同的sqlSession实例可以共享缓存。
缓存源码跟踪
第一次执行:
第二次执行: