MyBatis源码解析(三)—缓存篇

前言

大家好,这一篇文章是MyBatis系列的最后一篇文章,前面两篇文章《MyBatis源码解析(一)—构建篇》和《MyBatis源码解析(二)—执行篇》,主要说明了MyBatis是如何将我们的xml配置文件构建为其内部的Configuration对象和MappedStatement对象的,然后在第二篇我们说了构建完成后MyBatis是如何一步一步地执行我们的SQL语句并且对结果集进行封装的。那么这篇作为MyBatis系列的最后一篇,自然是要来聊聊MyBatis中的一个不可忽视的功能,一级缓存和二级缓存

何谓缓存?

虽然这篇说的是MyBatis的缓存,但是我希望正在学习计算机的小伙伴即使还没有使用过MyBatis框架也能看明白今天这篇文章。

**缓存是什么?**我来说说个人的理解,最后再上比较官方的概念。

缓存(Cache),顾名思义,有临时存储的意思。计算机中的缓存,我们可以直接理解为,存储在内存中的数据的容器,这与物理存储是有差别的,由于内存的读写速度比物理存储高出几个数量级,所以程序直接从内存中取数据和从物理硬盘中取数据的效率是不同的,所以有一些经常需要读取的数据,设计师们通常会将其放在缓存中,以便于程序对其进行读取。但是,缓存是有代价的,刚才我们说过,缓存就是在内存中的数据的容器,一条64G的内存条,通常可以买3-4块1T-2T的机械硬盘了,所以缓存不能无节制地使用,这样成本会剧增,所以一般缓存中的数据都是需要频繁查询,但是又不常修改的数据

而在一般业务中,查询通常会经过如下步骤。

读操作 --> 查询缓存中已经存在数据 -->如果不存在则查询数据库,如果存在则直接查询缓存–>数据库查询返回数据的同时,写入缓存中。

写操作 --> 清空缓存数据 -->写入数据库

MyBatis源码解析(三)—缓存篇_第1张图片

比较官方的概念

  ☞ 缓存就是数据交换的缓冲区(称作:Cache),当某一硬件要读取数据时,会首先从缓存汇总查询数据,有则直接执行,不存在时从内存中获取。由于缓存的数据比内存快的多,所以缓存的作用就是帮助硬件更快的运行。
  ☞ 缓存往往使用的是RAM(断电既掉的非永久存储),所以在用完后还是会把文件送到硬盘等存储器中永久存储。电脑中最大缓存就是内存条,硬盘上也有16M或者32M的缓存。
  ☞ 高速缓存是用来协调CPU与主存之间存取速度的差异而设置的。一般CPU工作速度高,但内存的工作速度相对较低,为了解决这个问题,通常使用高速缓存,高速缓存的存取速度介于CPU与主存之间。系统将一些CPU在最近几个时间段经常访问的内容存在高速缓存,这样就在一定程度上缓解了由于主存速度低造成的CPU“停工待料”的情况。
  ☞ 缓存就是把一些外存上的数据保存在内存上而已,为什么保存在内存上,我们运行的所有程序里面的变量都是存放在内存中的,所以如果想将值放入内存上,可以通过变量的方式存储。在JAVA中一些缓存一般都是通过Map集合来实现的。

MyBatis的缓存

在说MyBatis的缓存之前,先了解一下Java中的缓存一般都是怎么实现的,我们通常会使用Java中的Map,来实现缓存,所以在之后的缓存这个概念,就可以把它直接理解为一个Map,存的就是键值对。

  • 一级缓存简介

    MyBatis中的一级缓存,是默认开启且无法关闭的一级缓存默认的作用域是一个SqlSession,解释一下,就是当SqlSession被构建了之后,缓存就存在了,只要这个SqlSession不关闭,这个缓存就会一直存在,换言之,只要SqlSession不关闭,那么这个SqlSession处理的同一条SQL就不会被调用两次,只有当会话结束了之后,这个缓存才会一并被释放。

    虽说我们不能关闭一级缓存,但是作用域是可以修改的,比如可以修改为某个Mapper。

    一级缓存的生命周期:

    1、如果SqlSession调用了close()方法,会释放掉一级缓存PerpetualCache对象,一级缓存将不可用。

    2、如果SqlSession调用了clearCache(),会清空PerpetualCache对象中的数据,但是该对象仍可使用。

    3、SqlSession中执行了任何一个update操作(update()、delete()、insert()) ,都会清空PerpetualCache对象的数据,但是该对象可以继续使用。

    节选自:https://www.cnblogs.com/happyflyingpig/p/7739749.html

    MyBatis源码解析(三)—缓存篇_第2张图片

  • 二级缓存简介

    MyBatis的二级缓存是默认关闭的,如果要开启有两种方式:

    1. 在mybatis-config.xml中加入如下配置片段

      
      <settings>
             
          <setting name="cacheEnabled" value="true"/>
      
      settings>
      
    2. 在mapper.xml中开启

      
      
      <cache eviction="回收策略" type="缓存类"/>
      

    二级缓存的作用域与一级缓存不同,一级缓存的作用域是一个SqlSession,但是二级缓存的作用域是一个namespace,什么意思呢,你可以把它理解为一个mapper,在这个mapper中操作的所有SqlSession都可以共享这个二级缓存。但是假设有两条相同的SQL,写在不同的namespace下,那这个SQL就会被执行两次,并且产生两份value相同的缓存。

MyBatis缓存的执行流程

依旧是用前两篇的测试用例,我们从源码的角度看看缓存是如何执行的。

public static void main(String[] args) throws Exception {
    String resource = "mybatis.xml";
    InputStream inputStream = Resources.getResourceAsStream(resource);
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    SqlSession sqlSession = sqlSessionFactory.openSession();
    //从调用者角度来讲 与数据库打交道的对象 SqlSession
    DemoMapper mapper = sqlSession.getMapper(DemoMapper.class);
    Map<String,Object> map = new HashMap<>();
    map.put("id","2121");
    //执行这个方法实际上会走到invoke
    System.out.println(mapper.selectAll(map));
    sqlSession.close();
    sqlSession.commit();
  }

这里会执行到query()方法:

public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
	//二级缓存的Cache,通过MappedStatement获取
    Cache cache = ms.getCache();
    if (cache != null) {
      //是否需要刷新缓存
      //在