MyBatis 二级缓存详解(一)

简介: 我们在上一篇文章介绍了 MyBatis 的一级缓存的作用,如何开启,一级缓存的本质是什么,一级缓存失效的原因是什么?MyBatis 只有一级缓存吗?来找找答案吧!


MyBatis 二级缓存介绍


上一篇文章中我们介绍到了 MyBatis 一级缓存其实就是 SqlSession 级别的缓存,什么是 SqlSession 级别的缓存呢?一级缓存的本质是什么呢?以及一级缓存失效的原因?我希望你在看下文之前能够回想起来这些内容。


MyBatis 一级缓存最大的共享范围就是一个SqlSession内部,那么如果多个 SqlSession 需要共享缓存,则需要开启二级缓存,开启二级缓存后,会使用 CachingExecutor 装饰 Executor,进入一级缓存的查询流程前,先在CachingExecutor 进行二级缓存的查询,具体的工作流程如下所示

52.jpg

当二级缓存开启后,同一个命名空间(namespace) 所有的操作语句,都影响着一个 共同的 cache,也就是二级缓存被多个 SqlSession 共享,是一个全局的变量。当开启缓存后,数据的查询执行的流程就是 二级缓存 -> 一级缓存 -> 数据库。


二级缓存开启条件


二级缓存默认是不开启的,需要手动开启二级缓存,实现二级缓存的时候,MyBatis要求返回的POJO必须是可序列化的。开启二级缓存的条件也是比较简单,通过直接在 MyBatis 配置文件中通过

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

来开启二级缓存,还需要在 Mapper 的xml 配置文件中加入 <cache> 标签


设置 cache 标签的属性


cache 标签有多个属性,一起来看一些这些属性分别代表什么意义


  •  eviction: 缓存回收策略,有这几种回收策略
  •  LRU - 最近最少回收,移除最长时间不被使用的对象
  •  FIFO - 先进先出,按照缓存进入的顺序来移除它们
  •  SOFT - 软引用,移除基于垃圾回收器状态和软引用规则的对象
  •  WEAK - 弱引用,更积极的移除基于垃圾收集器和弱引用规则的对象


默认是 LRU 最近最少回收策略


  • flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值
  • readOnly: 是否只读;true 只读 ,MyBatis 认为所有从缓存中获取数据的操作都是只读操作,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 觉得数据可能会被修改
  • size : 缓存存放多少个元素
  • type: 指定自定义缓存的全类名(实现Cache 接口即可)
  • blocking:若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。


探究二级缓存


我们继续以 MyBatis 一级缓存文章中的例子为基础,搭建一个满足二级缓存的例子,来对二级缓存进行探究,例子如下(对 一级缓存的例子部分源码进行修改):

Dept.java


//存放在共享缓存中数据进行序列化操作和反序列化操作

//因此数据对应实体类必须实现【序列化接口】并提供 无参数的构造方法

public class Dept implements Serializable

myBatis-config.xml


在myBatis-config 中添加开启二级缓存的条件

<setting name="cacheEnabled" value="true"/>

DeptDao.xml


还需要在 Mapper 对应的xml中添加 cache 标签,表示对哪个mapper 开启缓存<cache>


对应的二级缓存测试类如下:

public class MyBatisSecondCacheTest {
    private SqlSession sqlSession;
    SqlSessionFactory factory;
    @Before
    public void start() throws IOException {
        InputStream is = Resources.getResourceAsStream("myBatis-config.xml");
        SqlSessionFactoryBuilder builderObj = new SqlSessionFactoryBuilder();
        factory = builderObj.build(is);
        sqlSession = factory.openSession();
    }
    @After
    public void destory(){
        if(sqlSession!=null){
            sqlSession.close();
        }
    }
    @Test
    public void testSecondCache(){
        //会话过程中第一次发送请求,从数据库中得到结果
        //得到结果之后,mybatis自动将这个查询结果放入到当前用户的一级缓存
        DeptDao dao =  sqlSession.getMapper(DeptDao.class);
        Dept dept = dao.findByDeptNo(1);
        System.out.println("第一次查询得到部门对象 = "+dept);
        //触发MyBatis框架从当前一级缓存中将Dept对象保存到二级缓存
        sqlSession.commit();
        // 改成 sqlSession.close(); 效果相同
        SqlSession session2 = factory.openSession();
        DeptDao dao2 = session2.getMapper(DeptDao.class);
        Dept dept2 = dao2.findByDeptNo(1);
        System.out.println("第二次查询得到部门对象 = "+dept2);
    }
}
public class MyBatisSecondCacheTest {
    private SqlSession sqlSession;    SqlSessionFactory factory;    @Before    public void start() throws IOException {        InputStream is = Resources.getResourceAsStream("myBatis-config.xml");        SqlSessionFactoryBuilder builderObj = new SqlSessionFactoryBuilder();        factory = builderObj.build(is);        sqlSession = factory.openSession();    }    @After    public void destory(){        if(sqlSession!=null){            sqlSession.close();        }    }
    @Test    public void testSecondCache(){        //会话过程中第一次发送请求,从数据库中得到结果        //得到结果之后,mybatis自动将这个查询结果放入到当前用户的一级缓存        DeptDao dao =  sqlSession.getMapper(DeptDao.class);        Dept dept = dao.findByDeptNo(1);        System.out.println("第一次查询得到部门对象 = "+dept);        //触发MyBatis框架从当前一级缓存中将Dept对象保存到二级缓存
        sqlSession.commit();        // 改成 sqlSession.close(); 效果相同
        SqlSession session2 = factory.openSession();        DeptDao dao2 = session2.getMapper(DeptDao.class);        Dept dept2 = dao2.findByDeptNo(1);        System.out.println("第二次查询得到部门对象 = "+dept2);    }}

测试二级缓存效果,提交事务,sqlSession 查询完数据后,sqlSession2相同的查询是否会从缓存中获取数据。


测试结果如下:53.jpg

通过结果可以得知,首次执行的SQL语句是从数据库中查询得到的结果,然后第一个 SqlSession 执行提交,第二个 SqlSession 执行相同的查询后是从缓存中查取的。


用一下这幅图能够比较直观的反映两次 SqlSession 的缓存命中

54.jpg

二级缓存失效的条件


与一级缓存一样,二级缓存也会存在失效的条件的,下面我们就来探究一下哪些情况会造成二级缓存失效


第一次SqlSession 未提交


SqlSession 在未提交的时候,SQL 语句产生的查询结果还没有放入二级缓存中,这个时候 SqlSession2 在查询的时候是感受不到二级缓存的存在的,修改对应的测试类,结果如下:

@Test
public void testSqlSessionUnCommit(){
  //会话过程中第一次发送请求,从数据库中得到结果
  //得到结果之后,mybatis自动将这个查询结果放入到当前用户的一级缓存
  DeptDao dao =  sqlSession.getMapper(DeptDao.class);
  Dept dept = dao.findByDeptNo(1);
  System.out.println("第一次查询得到部门对象 = "+dept);
  //触发MyBatis框架从当前一级缓存中将Dept对象保存到二级缓存
  SqlSession session2 = factory.openSession();
  DeptDao dao2 = session2.getMapper(DeptDao.class);
  Dept dept2 = dao2.findByDeptNo(1);
  System.out.println("第二次查询得到部门对象 = "+dept2);
}

产生的输出结果:

55.jpg

更新对二级缓存影响


与一级缓存一样,更新操作很可能对二级缓存造成影响,下面用三个 SqlSession来进行模拟,第一个 SqlSession 只是单纯的提交,第二个 SqlSession 用于检验二级缓存所产生的影响,第三个 SqlSession 用于执行更新操作,测试如下:

@Test
public void testSqlSessionUpdate(){
  SqlSession sqlSession = factory.openSession();
  SqlSession sqlSession2 = factory.openSession();
  SqlSession sqlSession3 = factory.openSession();
  // 第一个 SqlSession 执行更新操作
  DeptDao deptDao = sqlSession.getMapper(DeptDao.class);
  Dept dept = deptDao.findByDeptNo(1);
  System.out.println("dept = " + dept);
  sqlSession.commit();
  // 判断第二个 SqlSession 是否从缓存中读取
  DeptDao deptDao2 = sqlSession2.getMapper(DeptDao.class);
  Dept dept2 = deptDao2.findByDeptNo(1);
  System.out.println("dept2 = " + dept2);
  // 第三个 SqlSession 执行更新操作
  DeptDao deptDao3 = sqlSession3.getMapper(DeptDao.class);
  deptDao3.updateDept(new Dept(1,"ali","hz"));
  sqlSession3.commit();
  // 判断第二个 SqlSession 是否从缓存中读取
  dept2 = deptDao2.findByDeptNo(1);
  System.out.println("dept2 = " + dept2);
}

对应的输出结果如下

56.jpg

探究多表操作对二级缓存的影响


现有这样一个场景,有两个表,部门表dept(deptNo,dname,loc)和 部门数量表deptNum(id,name,num),其中部门表的名称和部门数量表的名称相同,通过名称能够联查两个表可以知道其坐标(loc)和数量(num),现在我要对部门数量表的 num 进行更新,然后我再次关联dept 和 deptNum 进行查询,你认为这个 SQL 语句能够查询到的 num 的数量是多少?来看一下代码探究一下

public class DeptNum {
    private int id;
    private String name;
    private int num;
    get and set...
}


相关文章
|
2月前
|
存储 缓存 NoSQL
mybatisplus一二级缓存
MyBatis-Plus 继承并优化了 MyBatis 的一级与二级缓存机制。一级缓存默认开启,作用于 SqlSession,适用于单次会话内的重复查询;二级缓存需手动开启,跨 SqlSession 共享,适合提升多用户并发性能。支持集成 Redis 等外部存储,增强缓存能力。
|
4月前
|
缓存 Java 数据库连接
Mybatis一级缓存详解
Mybatis一级缓存为开发者提供跨数据库操作的一致性保证,有效减轻数据库负担,提高系统性能。在使用过程中,需要结合实际业务场景选择性地启用一级缓存,以充分发挥其优势。同时,开发者需注意其局限性,并做好事务和并发控制,以确保系统的稳定性和数据的一致性。
137 20
|
12月前
|
缓存 Java 数据库连接
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
文章介绍了MyBatis的缓存机制,包括一级缓存和二级缓存的配置和使用,以及如何整合第三方缓存EHCache。详细解释了一级缓存的生命周期、二级缓存的开启条件和配置属性,以及如何通过ehcache.xml配置文件和logback.xml日志配置文件来实现EHCache的整合。
mybatis复习05,mybatis的缓存机制(一级缓存和二级缓存及第三方缓存)
|
6月前
|
缓存 Java 数据库连接
Mybatis一级缓存、二级缓存详讲
本文介绍了MyBatis中的查询缓存机制,包括一级缓存和二级缓存。一级缓存基于同一个SqlSession对象,重复查询相同数据时可直接从缓存中获取,减少数据库访问。执行`commit`操作会清空SqlSession缓存。二级缓存作用于同一namespace下的Mapper对象,支持数据共享,需手动开启并实现序列化接口。二级缓存通过将数据存储到硬盘文件中实现持久化,为优化性能,通常在关闭Session时批量写入缓存。文章还说明了缓存的使用场景及注意事项。
181 7
Mybatis一级缓存、二级缓存详讲
|
7月前
|
缓存 Java 数据库连接
十、MyBatis的缓存
十、MyBatis的缓存
122 6
|
SQL 缓存 Java
|
8月前
|
缓存 NoSQL Java
Mybatis学习:Mybatis缓存配置
MyBatis缓存配置包括一级缓存(事务级)、二级缓存(应用级)和三级缓存(如Redis,跨JVM)。一级缓存自动启用,二级缓存需在`mybatis-config.xml`中开启并配置映射文件或注解。集成Redis缓存时,需添加依赖、配置Redis参数并在映射文件中指定缓存类型。适用于查询为主的场景,减少增删改操作,适合单表操作且表间关联较少的业务。
151 6
|
10月前
|
SQL 缓存 Java
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
本文详细介绍了MyBatis的各种常见用法MyBatis多级缓存、逆向工程、分页插件 包括获取参数值和结果的各种情况、自定义映射resultMap、动态SQL
【详细实用のMyBatis教程】获取参数值和结果的各种情况、自定义映射、动态SQL、多级缓存、逆向工程、分页插件
|
9月前
|
缓存 Java 数据库连接
深入探讨:Spring与MyBatis中的连接池与缓存机制
Spring 与 MyBatis 提供了强大的连接池和缓存机制,通过合理配置和使用这些机制,可以显著提升应用的性能和可扩展性。连接池通过复用数据库连接减少了连接创建和销毁的开销,而 MyBatis 的一级缓存和二级缓存则通过缓存查询结果减少了数据库访问次数。在实际应用中,结合具体的业务需求和系统架构,优化连接池和缓存的配置,是提升系统性能的重要手段。
331 4
|
9月前
|
缓存 Java 数据库连接
MyBatis缓存机制
MyBatis提供两级缓存机制:一级缓存(Local Cache)默认开启,作用范围为SqlSession,重复查询时直接从缓存读取;二级缓存(Second Level Cache)需手动开启,作用于Mapper级别,支持跨SqlSession共享数据,减少数据库访问,提升性能。
136 1