目录
什么是缓存?
缓存是一种用于临时存储数据的技术,目的是提高数据访问的速度和效率。缓存将频繁访问的数据临时存储在快速访问的存储介质中,例如内存或高速缓存中,以便下次访问时能够更快地获取数据。
缓存的好处包括:
-
提高性能:缓存能够以更快的速度提供数据,避免了从较慢的存储介质中读取数据的延迟,从而提高了系统的性能和响应速度。
-
减轻负载:缓存可以减少对底层资源的访问需求,降低了系统的负载,提高了系统的扩展性和并发性能。
-
节约资源:通过减少对存储介质的访问次数,缓存可以节约系统资源的使用,延长硬件寿命,并降低了系统能耗。
Mybtais的一级缓存
一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问
使用场景
订单表与会员表是存在一对多的关系,为了尽可能减少join查询,进行了分阶段查询。即先查询出订单表,再根据member_id字段查询出会员表,最后进行数据整合。而如果订单表中存在重复的member_id,就会出现很多重复查询。
针对这种情况,mybatis通过一级缓存来解决:在同一次查询会话(SqlSession)中如果出现相同的语句及参数,就会从缓存中取出,不再走数据库查询。
一级缓存只能作用于查询会话中,所以也叫做会话缓存。
注:一级缓存默认开启,仅针对查询功能有效
我们来举例测试
@Test
public void testCache(){
SqlSession sqlSession = SqlSessionUtil.getSqlSession();
CacheMapper mapper = sqlSession.getMapper(CacheMapper.class);
long startTime=System.currentTimeMillis();
Emp emp = mapper.getEmpByEid(1);
System.out.println(emp);
long endTime=System.currentTimeMillis();
System.out.println("第一次执行花费时间为" + (endTime - startTime) + "ms");
long startTime2 =System.currentTimeMillis();
Emp emp2 = mapper.getEmpByEid(1);
System.out.println(emp);
long endTime2 =System.currentTimeMillis();
System.out.println("第二次执行花费时间为" + (endTime2 - startTime2) + "ms");
}
我们此时执行了两次getEmpByEid,但是日志显示仅执行了一次sql语句
第二次执行是直接从缓存中获取的数据,因此消耗了很少的时间
一级缓存的失效情况
- 不同的SqlSession对应不用的一级缓存
- 同一个SqlSession但是查询条件不同
- 同一个SqlSession两次查询期间执行了任何的一次增删改操作
- 同一个SqlSession两次查询期间手动清空了缓存
注:SpringBoot集成了Mybtis,但是每次执行sql语句时都重现创建了一个新的SqlSession对象。为了解决这个问题,我们加上@Transactional注解即可,mybatis 在查询时,会先从事务管理器中尝试获取 SqlSession
,取不到才会去创建新的 SqlSession
。所以只要将方法开启事务,那么一级缓存就会生效。
Mybatis的二级缓存
二级缓存时SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存;此后若是再次执行相同的查询语句,结果就会从缓存中获取
使用场景
业务系统中存在很多的静态数据如,字典表、菜单表、权限表等,这些数据的特性是不会轻易修改但又是查询的热点数据。
一级缓存针对的是同一个会话当中相同SQL,并不适合这情热点数据的缓存场景。
为了解决这个问题引入了二级缓存,它脱离于会话之外,多个会话可以使用相同的缓存。
开启步骤:
- 在Mybatis的全局配置文件中,设置全局配置属性cacheEnabled="true" ,默认就是true,不用设置也行
- 再Mapper映射文件中使用Cache标签
- 二级缓存必须在SqlSession关闭之后才会生效
- 查询的数据所转换的实体类类型必须实现序列化接口*
*序列化是将对象转换为可存储或传输的格式的过程。在计算机领域中,对象需要序列化是出于以下几个原因:
数据持久化:序列化允许将对象保存到磁盘或数据库中,以便在程序结束后或在不同的应用程序中重新加载和使用。通过序列化,对象的状态可以被永久保存,而不会受到内存中对象的生命周期的限制。
数据传输:在网络通信或分布式系统中,需要将对象从一个地方发送到另一个地方。序列化可以将对象转换为字节流或其他可传输的格式,可以通过网络传输或跨系统传递,以便在不同的计算机或平台上重建对象。
缓存和性能优化:序列化还可以在缓存系统和性能优化方面发挥作用。将对象序列化后存储在缓存中,可以提高访问性能,避免频繁地从数据库或其他耗时的数据源获取对象。此外,序列化还可以实现跨JVM进程的缓存共享,加快数据访问速度。
跨语言和跨平台:序列化使对象能够以独立于编程语言和操作系统的方式进行存储和传输。通过序列化,可以在不同的编程语言和不同的平台之间共享和交换数据,实现系统之间的互操作性。
需要注意的是,为了使对象能够序列化,对象的类必须实现序列化接口(如Java中的Serializable接口),并遵循序列化的规则。序列化还可能会引入一些安全性和版本控制的考虑,以确保序列化和反序列化的正确性和兼容性。
使二级缓存失效的情况
两次查询之间执行了任意的增删改,会使一级和二级缓存同时失效
eviction
属性:指定缓存的回收策略,默认是FIFO(先进先出),还可以选择LRU(最近最少使用)或者其他策略。flushInterval
属性:指定刷新缓存的间隔时间,单位为毫秒。默认情况下,缓存不会定时刷新,只有在提交或回滚事务时才会刷新。size
属性:指定缓存的最大大小,即缓存可以容纳的对象数目。readOnly
属性:指定缓存是否只读,默认为true。如果设置为true,则不会修改缓存中的对象,仅从缓存中获取数据。
Mybatis缓存查询的顺序
- 先查询二级缓存,因为二级缓存中可能会用其他程序已经查出来的数据,可以拿来直接使用。
- 如果二级缓存没有命中,再查询一级缓存
- 如果一级缓存也没有命中,则查询数据库
- SqlSession关闭后,一级缓存中的数据会写入二级缓存
尽量避免使用二级缓存
二级缓存的带来的数据隐患远远大于它的便利
首先我们要知道,它的缓存是以 namespace(mapper) 为单位的,不同 namespace 下的操作互不影响。且 insert/update/delete 操作会清空所在 namespace 下的全部缓存。
那么问题就出来了,假设现在有 ItemMapper 以及 XxxMapper,在 XxxMapper 中做了表关联查询,且做了二级缓存。此时在 ItemMapper 中将 item 信息给删了,由于不同 namespace 下的操作互不影响,XxxMapper 的二级缓存不会变,那之后再次通过 XxxMapper 查询的数据就不对了。
具体我们可以看这里 面试官:为什么不推荐使用 MyBatis 二级缓存?