解读Mybatis缓存机制

缓存及缓存机制 【解读Mybatis缓存机制】?传统的关系型数据库,十分强调数据的一致性,并为此降低读写性能付出了巨大的代价,虽然关系型数据库存储数据和处理数据的可靠性很不错,但一旦面对海量数据的处理的时候效率就会变得很差,特别是遇到高并发读写的时候性能就会下降的非常厉害。比如这样一个场景:使用sql语句(select * from user where id =1; )查询了数据库中id为1的数据,此时数据库会进行查询操作,并返回一个User类型的数据,没有进行增删改的操作,然后再次使用sql语句查询数据库,程序会重复上面的步骤.在这种频繁的查询操作下,对数据库来说是一个巨大的挑战.
?所以我们引入了缓存(cache),将经常不被修改并且频繁查询的数据放入内存中,减少对数据库的频繁读写操作,降低数据库的压力.当要读取数据时,会首先从内存中查找需要的数据,如果找到了则直接执行,找不到的话则从数据库中找。由于内存的运行速度比从数据库中查询快得多,故缓存的作用就是帮助查询更快地运行。
?Mysql缓存机制就是缓存sql 文本及缓存结果,用KV形式(是一种以键值对存储数据的一种形式,可以理解为一个大的map,每个键都会对应一个唯一的值。)保存再服务器内存中,如果运行相同的sql,服务器直接从缓存中去获取结果,不需要在再去解析、优化、执行sql。 如果这个表修改了,那么使用这个表中的所有缓存将不再有效,查询缓存值得相关条目将被清空。
?对于频繁更新的表,查询缓存不合适,对于一些不变的数据且有大量相同sql查询的表,查询缓存会节省很大的性能。
Mybatis缓存机制 ?和大多数持久层框架一样,MyBatis 同样提供了一级缓存和二级缓存的支持;
?一级缓存是Mybatis默认开启的,是基于 PerpetualCache 的 HashMap 本地缓存,存储作用域为 Session,当 Session flush (会话刷新)或 close 之后,该Session中的所有 Cache 就将清空。
?二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache、Hazelcast等。
一级缓存 ?在应用运行过程中,我们有可能在一次数据库会话中,执行多次查询条件完全相同的SQL,MyBatis提供了一级缓存的方案优化这部分场景,如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示。
解读Mybatis缓存机制
文章图片

?每个SqlSession中持有了Executor(执行器),每个Executor中有一个LocalCache(本地缓存)。当用户发起查询时,MyBatis根据当前执行的语句生成MappedStatement(在MyBatis启动时,会解析这些包含SQL的XML文件,并将其包装成为MapperStatement对象,并将MapperStatement注册到全局的configuration对象上),在Local Cache进行查询,如果缓存命中的话,直接返回结果给用户,如果缓存没有命中的话,查询数据库,结果写入Local Cache,最后返回结果给用户。具体实现类的类关系图如下图所示。
解读Mybatis缓存机制
文章图片

代码流程

  1. 第一次执行select完毕会将查到的数据写入SqlSession内的HashMap中缓存起来
  2. 第二次执行select会从缓存中查数据,如果select相同切传参数一样,那么就能从缓存中返回数据,不用去数据库了,从而提高了效率
注意事项:
  • 如果SqlSession执行了DML操作(insert、update、delete),并commit了,那么mybatis就会清空当前SqlSession缓存中的所有缓存数据,这样可以保证缓存中的存的数据永远和数据库中一致,避免出现脏读
  • 当一个SqlSession结束后那么他里面的一级缓存也就不存在了,mybatis默认是开启一级缓存,不需要配置
一级缓存的开启与关闭
  • 一级缓存默认开启
  • 关闭一级缓存需要在mybatis配置文件的settings标签设置
="localCacheScope" value="https://www.it610.com/article/SESSION"/>

导致一级缓存不命中的原因
  • 一级缓存关闭;
  • 一级缓存开启,但是使用了不同的SqlSession进行查询;
  • 使用相同的SqlSession,但是查询条件发生了变化;
  • 使用了相同的查询条件,但是两次查询之间SqlSession执行了commit、clearCache或close(关闭之后查询会报错)操作;
  • 使用了相同的查询条件,但是两次查询之间SqlSession执行了insert、update或delete操作,此时无论三种标签的
    flushCache 属性是否为 false,都会清空 sqlSession的缓存;
  • select标签的flushCache属性为true
清空一级缓存的操作
  • SqlSession调用commit方法;
  • SqlSession调用clearCache方法;
  • select标签的flushCache属性为true;
SqlSession执行了insert、update或delete操作,此时无论三种标签的flushCache属性是否为 false;
一级缓存实验 实验1
?开启一级缓存,范围为会话级别,调用三次Getbyid
代码如下所示:
public static void main(String[] args) throws IOException { } SqlSession ss; Userdao udao; //定义一个解析工具 @Before public voidsetup() throws IOException {String resource = "MybatisConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); ss = sqlSessionFactory.openSession(); udao=ss.getMapper(Userdao.class); } @Test public void Getbyid() { System.out.println(udao.Getbyid1(1)); System.out.println(udao.Getbyid1(1)); System.out.println(udao.Getbyid1(1)); System.out.println(udao.Getbyid1(1)); } @After public void finish() { ss.close(); }

执行结果:
解读Mybatis缓存机制
文章图片

实验2
?增加了对数据库的修改操作,验证在一次数据库会话中,如果对数据库发生了修改操作,一级缓存是否会失效。
代码如下
public static void main(String[] args) throws IOException { } SqlSession ss; Userdao udao; //定义一个解析工具 @Before public voidsetup() throws IOException {String resource = "MybatisConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); ss = sqlSessionFactory.openSession(); udao=ss.getMapper(Userdao.class); } @Test public void Getbyid() { System.out.println(udao.Getbyid1(1)); System.out.println(udao.Getbyid1(1)); User user = new User(); user.setId(1); user.setSex("男"); System.out.println("更新数据"+udao.UpdateUser(user)); System.out.println(udao.Getbyid1(1)); System.out.println(udao.Getbyid1(1)); } @After public void finish() { ss.close(); }

执行结果:
解读Mybatis缓存机制
文章图片

实验3
?开启两个SqlSession,在sqlSession1中查询数据,使一级缓存生效,在sqlSession2中更新数据库,验证一级缓存只在数据库会话内部共享。
执行代码:
public static void main(String[] args) throws IOException { } SqlSession ss; Userdao udao; SqlSession ss2; Userdao udao2; //定义一个解析工具 @Before public voidsetup() throws IOException {String resource = "MybatisConfig.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); ss = sqlSessionFactory.openSession(); udao=ss.getMapper(Userdao.class); ss2 = sqlSessionFactory.openSession(); udao2=ss2.getMapper(Userdao.class); } @Test public void Getbyid() { System.out.println(udao.Getbyid(1)); System.out.println(udao.Getbyid(1)); System.out.println(udao2.Getbyid(1)); System.out.println(udao2.Getbyid(1)); } @After public void finish() { ss.close(); }

执行结果:
解读Mybatis缓存机制
文章图片

一级缓存小结
  • MyBatis一级缓存的生命周期和SqlSession一致。
  • MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺
  • MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。
二级缓存 ?在上文中提到的一级缓存中,其最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,则需要使用到二级缓存。二级缓存是mapper(接口)级别的缓存,也就是同一个namespace的mappe.xml(映射文件),当多个SqlSession使用同一个Mapper操作数据库的时候,得到的数据会缓存在同一个二级缓存区域.开启二级缓存后,会使用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先在CachingExecutor进行二级缓存的查询,具体的工作流程如下所示。
解读Mybatis缓存机制
文章图片

?二级缓存开启后,同一个namespace下的所有操作语句,都影响着同一个Cache,即二级缓存被多个SqlSession共享,是一个全局的变量。
?当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。
二级缓存的配置 ?要正确的使用二级缓存,需完成如下配置的。在MyBatis的全局配置文件中开启二级缓存。
> ="cacheEnabled" value="https://www.it610.com/article/true"/>默认是false:关闭二级缓存 >

在MyBatis的映射配置文件XML中配置cache或者 cache-ref 。
cache标签用于声明这个namespace使用二级缓存,并且书写属性可以自定义配置。
  • type:cache使用的类型,默认是PerpetualCache(Mybatis自带的二级缓存),也可以使用第三方的插件
  • eviction: 定义回收的策略,常见的有FIFO,LRU。
  • flushInterval: 配置一定时间自动刷新缓存,单位是毫秒。
  • size: 最多缓存对象的个数。
  • readOnly: 是否只读,若配置可读写,则需要对应的实体类能够序列化。
  • blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
当前mapper下所有语句开启二级缓存

cache-ref代表引用别的命名空间的Cache配置,两个命名空间的操作使用的是同一个Cache。

po 类(实体类)实现 Serializable 序列化接口
public class User implements Serializable{....}

二级缓存实验 实验1
测试二级缓存效果,不提交事务,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。
执行代码:
@Test public void testCacheWithoutCommitOrClose() throws Exception { SqlSession sqlSession1 = factory.openSession(true); SqlSession sqlSession2 = factory.openSession(true); StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1)); System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1)); }

执行结果:
解读Mybatis缓存机制
文章图片

实验2
测试二级缓存效果,当提交事务时,sqlSession1查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。
执行代码:
@Test public void testCacheWithCommitOrClose() throws Exception { SqlSession sqlSession1 = factory.openSession(true); SqlSession sqlSession2 = factory.openSession(true); StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1)); sqlSession1.commit(); System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1)); }

执行结果:
解读Mybatis缓存机制
文章图片

实验3
测试update操作是否会刷新该namespace下的二级缓存。
执行代码:
@Test public void testCacheWithUpdate() throws Exception { SqlSession sqlSession1 = factory.openSession(true); SqlSession sqlSession2 = factory.openSession(true); SqlSession sqlSession3 = factory.openSession(true); StudentMapper studentMapper = sqlSession1.getMapper(StudentMapper.class); StudentMapper studentMapper2 = sqlSession2.getMapper(StudentMapper.class); StudentMapper studentMapper3 = sqlSession3.getMapper(StudentMapper.class); System.out.println("studentMapper读取数据: " + studentMapper.getStudentById(1)); sqlSession1.commit(); System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1)); studentMapper3.updateStudentName("方方",1); sqlSession3.commit(); System.out.println("studentMapper2读取数据: " + studentMapper2.getStudentById(1)); }

执行结果:
解读Mybatis缓存机制
文章图片

本人个人网站: https://www.icnfox.cn 欢迎来访
有任何问题可以在个人网站的评论区留言,看到就会第一时间回复 啾咪ヾ(≧▽≦*)o

    推荐阅读