首页 > 编程语言 >Mybatis源码(4)一级缓存和二级缓存

Mybatis源码(4)一级缓存和二级缓存

时间:2024-07-08 10:56:43浏览次数:10  
标签:缓存 一级 cache 查询 二级缓存 源码 key Mybatis

1、目标

本文的主要目标是探究Mybatis源码中的一级缓存和二级缓存,先分析标签作用,然后分析一级缓存和二级缓存的源码

2、一级缓存的标签

2.1 cacheEnabled标签

在这里插入图片描述
cacheEnabled是控制二级缓存是否可以使用,默认值是true表示二级缓存可以使用(不表示开启了二级缓存),但是一级缓存始终存在
cacheEnabled=true:二级缓存可以使用(不表示开启了二级缓存),查询流程是先查询二级缓存的执行器CacheExecutor,接着查询一级缓存的执行器BaseExecutor,如果都没有最后查询数据库
cacheEnabled=false:二级缓存不能使用,一级缓存存在,查询流程是先查询一级缓存的执行器BaseExecutor,如果没有就查询数据库
注意:二级缓存可以使用不表示二级缓存已经开启了

cacheEnabled标签用在mybatis-config.xml文件中,在settings标签内配置这个标签

<settings>
    <setting name="cacheEnabled" value="false"/>
</settings>

2.2 localCacheScope标签

在这里插入图片描述
localCacheScope是控制一级缓存的作用范围,有效值是SESSION和STATEMENT,默认值是SESSION
localCacheScope=SESSION:一级缓存在同一个SqlSession的执行过程中遇到相同的sql语句会查询一级缓存并返回,当执行update语句时一级缓存会失效
localCacheScope=STATEMENT:一级缓存在执行完成sql语句就会失效

localCacheScope标签用在mybatis-config.xml文件中,在settings标签内配置这个标签

<settings>
    <setting name="localCacheScope" value="STATEMENT"/>
</settings>

3、二级缓存的标签

cache标签

在这里插入图片描述

cache标签是开启二级缓存,作用范围是一个mapper接口

开启二级缓存必须满足的三个条件:
① cacheEnabled=true
② 在mapper.xml文件中设置cache标签
③ 同时sqlSession必须commit才会将数据存放到二级缓存中并清除一级缓存

cache标签用在mapper.xml文件中,在mapper标签内部

<mapper namespace="org.apache.mybatisDemo.BlogMapper">
    <select id="selectBlog" resultType="org.apache.mybatisDemo.Blog">
        select * from Blog where author = #{author}
    </select>
    <cache/>
</mapper>

4、一级缓存的源码分析

MyBatis的一级缓存,使用的是PerpetualCache

二级缓存使用的是TransactionalCache

4.1 BaseExecutor的一级缓存

在这里插入图片描述

BaseExecutor执行器包含一级缓存localCache,它是PerpetualCache对象

public class PerpetualCache implements Cache {

  private final String id;

  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

cache是一个HashMap,还保存了id,equals方法是如果id相同就可以,hashCode方法是返回id的hashCode,其中putObject方法会向map添加元素,getObject方法从map获取元素,removeObject方法删除map的元素,clear方法是清除map的所有元素(size变成0)

4.2 cacheEnabled=false不走CacheExecutor,走BaseExecutor

查询流程:

try (SqlSession session = sqlSessionFactory.openSession()) {
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    Blog blog = mapper.selectBlog("lisi");
    System.out.println(blog);
    blog = mapper.selectBlog("lisi");
    System.out.println(blog);
}

同一个SqlSession对象查询同一个接口两次,可以看到查询只会走BaseExecutor执行器的一级缓存,第一次查询会查询数据库并保存到一级缓存,第二次查询会查询一级缓存并返回

第一次查询:

在这里插入图片描述

cacheEnabled=false,调用selectList方法查询不走CacheExecutor,直接走BaseExecutor,发现一级缓存没有就查询数据库

其中,localCache.getObject(key)方法会调用PerpetualCache对象的cache.get(key)方法,其中cache是一个HashMap

在这里插入图片描述

一级缓存不存在数据就查询数据库得到结果list,然后放到BaseExecutor执行器的一级缓存localCache这个map中,key是查询的sql语句,value是查询结果list

其中,localCache.putObject(key, list)方法会调用PerpetualCache对象的cache.put(key, value),其中cache是一个HashMap

在这里插入图片描述

将数据库查询结果存放到一级缓存后,会判断localCacheScope如果是STATEMENT就会清除一级缓存,这里localCacheScope是SESSION因此不会清除一级缓存

localCacheScope表示作用范围,如果是STATEMENT表示作用域是查询的sql语句,如果是SESSION表示作用域是同一个SqlSession对象

清除一级缓存会调用BaseExecutor的localCache.clear()方法,它会调用PerpetualCache的cache.clear()方法,cache是一个HashMap

第二次查询:

在这里插入图片描述

第二次查询发现BaseExecutor执行器的一级缓存存在数据就直接返回结果,不会查询数据库了,从而实现了同一个SqlSession对象查询多次相同的sql语句返回相同的结果是查询一级缓存的

4.3 cacheEnabled=true走CacheExecutor,走BaseExecutor

查询流程:

try (SqlSession session = sqlSessionFactory.openSession()) {
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    Blog blog = mapper.selectBlog("lisi");
    System.out.println(blog);
    blog = mapper.selectBlog("lisi");
    System.out.println(blog);
}

同一个SqlSession对象查询同一个接口两次,可以看到查询会先走CacheExecutor的二级缓存,然后走BaseExecutor执行器的一级缓存,如果都没有会查询数据库,第一次查询会查询二级缓存但此时二级缓存cache为空表示没有开启二级缓存,因此会查询一级缓存发现没有就查询数据库并保存到一级缓存,第二次查询会查询二级缓存发现没有然后查询一级缓存发现存在就返回结果

第一次查询:

在这里插入图片描述

cacheEnabled=true,调用selectList方法查询会先走CachingExecutor执行器的二级缓存,发现没有开启二级缓存就查询BaseExecutor执行器的一级缓存

在这里插入图片描述

查询BaseExecutor执行器的一级缓存发现没有就查询数据库并保存到一级缓存中

其中,localCache.getObject(key)方法会调用PerpetualCache对象的cache.get(key)方法,其中cache是一个HashMap

在这里插入图片描述

一级缓存不存在数据就查询数据库得到结果list,然后放到BaseExecutor执行器的一级缓存localCache这个map中,key是查询的sql语句,value是查询结果list

其中,localCache.putObject(key, list)方法会调用PerpetualCache对象的cache.put(key, value),其中cache是一个HashMap

在这里插入图片描述

将数据库查询结果存放到一级缓存后,会判断localCacheScope如果是STATEMENT就会清除一级缓存,这里localCacheScope是SESSION因此不会清除一级缓存

localCacheScope表示作用范围,如果是STATEMENT表示作用域是查询的sql语句,如果是SESSION表示作用域是同一个SqlSession对象

清除一级缓存会调用BaseExecutor的localCache.clear()方法,它会调用PerpetualCache的cache.clear()方法,cache是一个HashMap

第二次查询:

在这里插入图片描述

第二次查询二级缓存还是没有就查询一级缓存,因为二级缓存没有开启

查询一级缓存发现有就直接返回

在这里插入图片描述

第二次查询发现BaseExecutor执行器的一级缓存存在数据就直接返回结果,不会查询数据库了,从而实现了同一个SqlSession对象查询多次相同的sql语句返回相同的结果是查询一级缓存的

4.4 cacheEnabled和localCacheScope的作用

cacheEnabled=true比cacheEnabled=false只是多走了二级缓存的执行器CachingExecutor,但是这两种情况都没有开启二级缓存,因此这两种情况数据还是存储在一级缓存中。
cacheEnabled是控制二级缓存是否可以使用,默认值是true表示二级缓存可以使用(不表示开启了二级缓存),但是一级缓存始终存在

localCacheScope如果是STATEMENT就会清除一级缓存
localCacheScope表示作用范围,如果是STATEMENT表示作用域是查询的sql语句,如果是SESSION表示作用域是同一个SqlSession对象

5、二级缓存的源码分析

开启二级缓存必须满足的三个条件:
① cacheEnabled=true
② 在mapper.xml文件中设置cache标签
③ 同时sqlSession必须commit才会将数据存放到二级缓存中并清除一级缓存
如果没有执行commit方法会将数据保存在一级缓存中,二级缓存没有数据

MyBatis的一级缓存,使用的是PerpetualCache

二级缓存使用的是TransactionalCache

5.1 CachingExecutor的二级缓存

在这里插入图片描述

CachingExecutor执行器包含一个TransactionalCacheManager对象,它是二级缓存

public class TransactionalCacheManager {

  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }

  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

  public void putObject(Cache cache, CacheKey key, Object value) {
    getTransactionalCache(cache).putObject(key, value);
  }

  public void commit() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.commit();
    }
  }

  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  private TransactionalCache getTransactionalCache(Cache cache) {
    return MapUtil.computeIfAbsent(transactionalCaches, cache, TransactionalCache::new);
  }

}

TransactionalCacheManager是管理TransactionalCache的对象,transactionalCaches是一个HashMap,map的key是Cache对象,map的value是TransactionalCache对象,其中getObject方法可以创建transactionalCaches这个map对象并调用TransactionalCache对象的getObject方法,putObject方法可以创建transactionalCaches这个map对象并调用TransactionalCache对象的putObject方法,commit方法会遍历map的value并循环调用TransactionalCache对象的commit方法

在这里插入图片描述

TransactionalCacheManager对象中的transactionalCaches是一个HashMap,map的key是Cache对象,它就是MappedStatement对象中的Cache对象

public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  private final Cache delegate;
  private boolean clearOnCommit;
  private final Map<Object, Object> entriesToAddOnCommit;
  private final Set<Object> entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<>();
    this.entriesMissedInCache = new HashSet<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    }
    return object;
  }

  @Override
  public void putObject(Object key, Object object) {
    entriesToAddOnCommit.put(key, object);
  }

  @Override
  public Object removeObject(Object key) {
    return null;
  }

  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

  public void commit() {
    if (clearOnCommit) {
      delegate.clear();
    }
    flushPendingEntries();
    reset();
  }

  public void rollback() {
    unlockMissedEntries();
    reset();
  }

  private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  private void flushPendingEntries() {
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  private void unlockMissedEntries() {
    for (Object entry : entriesMissedInCache) {
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifying a rollback to the cache adapter. "
            + "Consider upgrading your cache adapter to the latest version. Cause: " + e);
      }
    }
  }

}

TransactionalCache对象包含delegate、entriesToAddOnCommit、entriesMissedInCachedelegate表示实际的Cache,entriesToAddOnCommit是一个HashMap,它存放了准备提交的putObject的数据(但是还没有存放到二级缓存),entriesMissedInCache是一个HashSet,它存放了getObject方法获取数据的时候为null的key

putObject方法会将数据存放到entriesToAddOnCommit这个map中,但是还没有存放到二级缓存中

getObject方法会调用delegate的getObject方法,即从二级缓存中获取数据,如果二级缓存中没有这个key就将这个key存放到entriesMissedInCache这个set中,它的作用是在执行commit方法时可以将数据库不存在的key对应的value设置成null并存放到二级缓存中

commit方法会调用delegate.putObject方法,先将entriesToAddOnCommit存放到二级缓存中,然后将entriesMissedInCache中的key对应的value设置成null并调用delegate.putObject方法存储到二级缓存中,这样的话数据库不存在的数据会保存到二级缓存中,它的value为null

5.2 执行流程

查询流程:

try (SqlSession session = sqlSessionFactory.openSession()) {
    BlogMapper mapper = session.getMapper(BlogMapper.class);
    Blog blog = mapper.selectBlog("lisi");
    session.commit();   // 必须加上commit方法二级缓存才生效
    System.out.println(blog);
    blog = mapper.selectBlog("lisi");
    session.commit();   // 必须加上commit方法二级缓存才生效
    System.out.println(blog);
}

同一个SqlSession对象查询同一个接口两次,可以看到查询先走CachingExecutor执行器的二级缓存,然后走BaseExecutor执行器的一级缓存,如果都没有就查询数据库,第一次查询会查询数据库并保存到一级缓存但还没有保存到二级缓存中,commit提交的时候会删除一级缓存并将数据存储到二级缓存中,并且对于数据库不存在的key会将value=null存储到二级缓存中,第二次查询会查询二级缓存并返回

注意:二级缓存生效的话必须每次执行完成一个sql语句就执行sqlSession对象的commit方法将数据存储到二级缓存中

第一次查询:

1、执行selectList方法

在这里插入图片描述

开启了二级缓存会查询二级缓存的数据发现没有就查询一级缓存,发现也没有就查询数据库,然后将数据存放到一级缓存,还会将数据保存到二级缓存的属性中,但是没有真正的保存到二级缓存中

在这里插入图片描述

二级缓存没有数据会查询一级缓存和数据库

在这里插入图片描述

一级缓存不存在数据就查询数据库得到结果list,然后放到BaseExecutor执行器的一级缓存localCache这个map中,key是查询的sql语句,value是查询结果list

在这里插入图片描述

tcm.putObject(cache, key, list) 会将查询的数据保存到entriesToAddOnCommit中,但此时还没有将数据存放到二级缓存中

2、SqlSession对象执行commit方法

public void commit(boolean required) throws SQLException {
 	delegate.commit(required);
 	tcm.commit();
}

SqlSession对象执行commit方法会先调用delegate.commit方法,然后调用tcm.commit()方法

在这里插入图片描述

delegate.commit方法会调用BaseExecutor中的localCache的clear方法,清除一级缓存

在这里插入图片描述

tcm.commit()方法会将数据保存到二级缓存中

第二次查询:

1、执行selectList方法

在这里插入图片描述

第二次查询的时候发现二级缓存已经存在结果就返回结果

5.3 cache标签的作用

cache标签的作用是开启二级缓存,第一次查询的时候会将数据存放到一级缓存中,执行commit方法会将数据存放到二级缓存中并删除一级缓存,第二次查询的时候从二级缓存查询数据并返回结果

标签:缓存,一级,cache,查询,二级缓存,源码,key,Mybatis
From: https://blog.csdn.net/weixin_43823462/article/details/140244040

相关文章

  • Java计算机毕业设计的软件推荐平台(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展,软件应用已成为人们日常生活和工作中不可或缺的一部分。然而,面对海量的软件资源,用户往往难以快速找到符合自身需求的软件,同时......
  • Java计算机毕业设计的思政分享平台(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景在全球化与信息化高速发展的今天,思想政治教育面临着前所未有的挑战与机遇。一方面,多元文化的交融与碰撞使得青年学生的思想观念更加复杂多样;另一方面......
  • Java计算机毕业设计的图书管理系统(开题报告+源码+论文)
    本系统(程序+源码)带文档lw万字以上 文末可获取一份本项目的java源码和数据库参考。系统程序文件列表开题报告内容研究背景随着信息技术的飞速发展和知识经济的兴起,图书馆作为知识传播与存储的重要机构,其管理效率与服务质量直接关系到读者的满意度与知识获取的便捷性。传统......
  • 超市收银系统源码
    今天给大家分享一套线上线下打通的收银系统,安卓/win双端线下收银台,可DIY、多模板的三端线上小程序商城,除此之外ERP进销存管理、商品管理、会员营销都很完善。重点是系统支持OEM贴牌独立部署和全开源源码,非常适合一些正在寻找现成系统,想在其基础上进行自己的个性化开发延伸的公......
  • 课程设计-基于Springboot+Vue的网上商城购物系统的设计与实现(源码+LW+包运行)
    源码获取地址:https://download.csdn.net/download/u011832806/89426605系统演示视频:链接:https://pan.baidu.com/s/1p9Xv9VrlNXSyNXRkdhccPg?pwd=xfdy一.系统概述网上商城购物系统主要是为了提高工作人员的工作效率和更方便快捷的满足用户,更好存储所有数据信息及快速方......
  • Spring Boot3整合Mybatis Plus,数据库为MySQL
    项目结构如下:注意不需要任何XML文件1.导入依赖除了SpringBoot创建时自带的依赖,还需要加入:<!--MybatisPlus依赖--><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-spring-boot3-starter</artifactId><version>3.5.7</version&g......
  • SSM-企业人事信息管理系统-98194(免费领源码)可做计算机毕业设计JAVA、PHP、爬虫、APP、
    企业人事信息管理系统的设计与实现摘 要由于数据库和数据仓库技术的快速发展,企业人事信息管理系统建设越来越向模块化、智能化、自我服务和管理科学化的方向发展。人事管理系统对处理对象和服务对象,自身的系统结构,处理能力,都将适应技术发展的要求发生重大的变化。企业人事......
  • Spring源码(一) 如何阅读 Spring 源码
    学习Spring的源码,也可以通过SpringBoot搭环境。不管是什么源码,最好写个demo,跑起来,然后从常用的类和方法入手,跟踪调试。配置对象新建一个SpringBoot的项目,详情见:https://blog.csdn.net/sinat_32502451/article/details/133039001接着在com.example.demo.model......
  • 基于Java“萌宠之家”宠物综合服务平台设计实现(源码+lw+部署文档+讲解等)
    \n文末获取源码联系感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询系统介绍:互联网发展至今,无论是其理论还是技术都已经成熟,而且它广泛参与在社会中的方方面面。它让信息都可以通过网络传播,搭配信息管理工具可以很好地为人们提供服务。针......
  • 基于Java“镜头人生”约拍网站系统设计实现(源码+lw+部署文档+讲解等)
    \n文末获取源码联系感兴趣的可以先收藏起来,大家在毕设选题,项目以及论文编写等相关问题都可以给我加好友咨询系统介绍:现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本“镜头人生”约拍网站就是在这样的大......