Mybatis—缓存机制

Mybatis—缓存机制

老规矩,先看官网:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache

一、概念

缓存是什么以及使用缓存的好处大家应该都了解,不了解的话说明你需要努力了,该充电了少年。Mybatis作为司职与数据库打交道的框架,当然会有缓存机制啦,那么在Mybatis中是如何使用缓存的呢?

1、Mybatis中的缓存机制

如果没有缓存,那么每次查询的时候,都要从数据库中检索数据,由于IO的瓶颈,导致整个系统的瓶颈受限于IO的缓慢速度,所以在很多情况下,都会使用到缓存机制。

如果执行SQL语句,会优先从缓存中查询是否能获取到结果。如果获取不到,才会去数据库中查数据,这意味着使用Mybatis查询数据的时候,会把查询到的结果放入到缓存中

2、Mybatis中的缓存分类

  1. 一级缓存:表示sqlSession级别的缓存,每次查询的时候都会开启一个会话,此会话相当于一个连接,关闭之后自动失效。
  2. 二级缓存:全局范围内的缓存,sqlSession关闭之后才会生效
  3. 第三方缓存:继承第三方的组件,来充当缓存的作用

3、怎么区分sql语句是走数据库查询还是走缓存路径?

怎么区分我们的sql语句是走的缓存还是走的数据库查询呢?

如果走缓存的话,是不会执行SQL语句的;而如果没走缓存,必然要从数据库查询,此时就会执行具体的SQL语句。这一点我们可以通过log4j打印执行sql的日志信息验证。

二、一级缓存

1、基本使用

一级缓存表示将查询的数据存储在SqlSession中,一旦关闭SqlSession,缓存就会失效。

一级缓存是默认开启的,在同一个会话之内,如果执行了多条相同的sql语句,那么除了第一条sql语句会从数据库查询,后续的sql都是从缓存中查询的。
在这里插入图片描述

2、一级缓存的失效场景

在大部分的情况下一级缓存是可行的,但是有几种特殊的情况会造成一级缓存失效:

1、一级缓存是sqlSession级别的缓存,如果在应用程序中开启了多个sqlsession,不同的SqlSession之间的缓存不共享

一个方法可能会开启多个SqlSession,如果不在同一个SqlSession之内的sql语句,是不能够共享缓存的。
在这里插入图片描述
2、在编写查询的sql语句的时候,一定要注意传递的参数,如果参数不一致,那么也不会缓存结果

  • 如果传入的参数是基本数据类型,我们很容易就能判断,多条相同的sql语句传入的参数是不一致的,此时并不会使用一级缓存。
  • 但是如果语句中传入的参数是引用类型,是一个对象,多条相同的sql语句,传入的参数都是这个对象,此时要注意,如果这个对象里面的属性值是不一样的,那么也不会使用一级缓存。
    在这里插入图片描述

3、当在一个会话中,在多次查询之间,如果数据库中数据被修改,一级缓存也会失效。

也就是说如果存在并发场景,在另一个SqlSession中修改了数据库中的数据,本SqlSession是不受影响的。

 @Test
    public void test03(){
    
    
        SqlSession sqlSession = sqlSessionFactory.openSession();
        EmpDao mapper = sqlSession.getMapper(EmpDao.class);
        Emp empByEmpno = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno);
        System.out.println("================================");
        empByEmpno.setEname("zhangsan");
        int i = mapper.updateEmp(empByEmpno);
        System.out.println(i);
        System.out.println("================================");
        Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno1);
        sqlSession.close();
    }

4、如果在一个会话过程中,手动清空了缓存,那么一级缓存也会失效。

@Test
    public void test03(){
    
    
        SqlSession sqlSession = sqlSessionFactory.openSession();
        EmpDao mapper = sqlSession.getMapper(EmpDao.class);
        Emp empByEmpno = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno);
        System.out.println("================================");
        System.out.println("手动清空缓存");
        sqlSession.clearCache();
        System.out.println("================================");
        Emp empByEmpno1 = mapper.findEmpByEmpno(1111);
        System.out.println(empByEmpno1);
        sqlSession.close();
    }

5、总结

缓存是需要与数据库中的数据保持一致性的,如果多次查询过程中,数据库表中的数据发生了修改,那么缓存就理所当然的要失效了。类比于,在多线程情况下,我们要保证数据的强一致性。

三、二级缓存

默认情况下,只启用了本地的SqlSession缓存,也就是一级缓存,它仅仅对一个会话中的数据进行缓存。

二级缓存表示全局缓存,必须要等到SqlSession关闭后才会才会把当前SqlSession的缓存数据存放到二级缓存。

要启用全局的二级缓存,需要在你的Mapper代理文件中添加一行:

<cache></cache>

当添加上该标签之后,会有如下效果:

  • 映射语句文件中的所有 select 语句的结果将会被缓存
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

1、基本使用

1、在Mybatis的全局配置文件中添加二级缓存的配置

mybatis-config.xml

<settings>
    <!--开启驼峰命名规则-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
    <!--开启二级(全局)缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

2、如果要想在某一个Dao接口的所有方法上使用二级缓存,要在其对应的Mapping代理的xml文件中添加配置

比如我想让EmpDao接口定义的方法执行sql语句的时候使用二级缓存,就要在EmpDao接口mapping映射的XML文件中添加如下配置

EmpDao.xml

<!--开启二级缓存-->
<cache></cache>

3、对应的java实体类bean必须要实现序列化的接口

Emp.java

public class Emp implements Serializable {
    
    
	.......
}

4、进行单元测试

这里我们使用上面一级缓存失效的同一个单元测试test06,原封不动的再次执行。

这次我们可以发现,虽然一级缓存失效了,第三条sql语句和前两条并不在同一个SqlSession,但是仍然是走了缓存,这就是二级缓存的作用了。

2、二级缓存的属性

开启二级缓存:使用标签

<cache></cache>

此标签的属性:
eviction:表示缓存回收策略,默认时LRU(重要)
LRU:最近最少使用,表示移除最长时间不用的对象(重要)
FIFO:先进先出,表示按照对象进入缓存的顺序进行移除(重要)
SOFT:软引用,移除基于垃圾回收器状态和软引用规则的对象
WEAK:弱引用,更积极地移除基于垃圾收集器状态和弱引用规则的对象

flushInternal:缓存刷新间隔,单位毫秒。
默认情况是不设置,也就是没有刷新间隔,缓存仅仅调用语句时刷新。
我们一般情况下也不会设置,了解即可。

size:引用数目,正整数。
代表缓存最多可以存储多少个对象,太大容易导致内存溢出,默认是1024。
我们一般情况下也不会设置,了解即可。

readonly:只读缓存,true/false
true:只读缓存,会给所有调用这返回缓存对象的相同实例,因此这些对象不能被修改。
false:读写缓存,会返回缓存对象的拷贝(序列化实现),这种方式比较安全,默认值就是false。

官网示例:

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

3、缓存命中率

缓存命中率就是针对二级缓存来说的,一级缓存并没有缓存命中率的概念。

单元测试:总共执行了四次查询操作

@Test
    public void test08(){
    
    
        SqlSession sqlSession1 = sqlSessionFactory.openSession(true);
        EmpDao mapper1 = sqlSession1.getMapper(EmpDao.class);
        Emp emp1 = mapper1.selectEmpByEmpno(7369);
        System.out.println(emp1);
        System.out.println("--------------------------------------------");
        
        Emp emp2 = mapper1.selectEmpByEmpno(7369);
        System.out.println(emp2);
        //关闭会话之后,一级缓存失效
        sqlSession1.close();
        System.out.println("--------------------------------------------");
        
        //重新开启一个SqlSession
        SqlSession sqlSession2 = sqlSessionFactory.openSession(true);
        EmpDao mapper2 = sqlSession2.getMapper(EmpDao.class);
        Emp emp3 = mapper2.selectEmpByEmpno(7369);
        System.out.println(emp3);

        System.out.println("--------------------------------------------");
        Emp emp4 = mapper2.selectEmpByEmpno(7369);
        System.out.println(emp4);
        sqlSession2.close();
    }

执行结果:
在这里插入图片描述

4、缓存查询顺序

当查询数据的时候,应该是先查询一级缓存还是先查询二级缓存?

我们可以通过案例演示,在原来的4条sql语句的基础之上,再新增两条sql:

@Test
public void test08(){
    
    
	........
    System.out.println("--------------------------------------------");
    Emp emp5 = mapper2.selectEmpByEmpno(7499);//注意查询的数据是不同的,这次查询的编号是7499
    System.out.println(emp5);

    System.out.println("--------------------------------------------");
    Emp emp6 = mapper2.selectEmpByEmpno(7499);//这次查询是走一级缓存还是二级缓存呢?
    System.out.println(emp6);
    sqlSession2.close();
}

执行结果分析:
在这里插入图片描述
总结:会先尝试从二级缓存中获取数据,获取不到才会走一级缓存,一级缓存也没有就会去数据库查数据。
在这里插入图片描述
查询顺序:二级缓存–》 一级缓存 --》数据库sql

四、整合第三方缓存

我们可以实现cache接口来自定义实现缓存,但是几乎没人这么干,一般使用第三方缓存。

通过继承第三方的组件,来充当缓存的作用。

第三方缓存主要使用两种:Redis、ehcache。Mybatis推荐使用ehcache。

以下我们简单演示如何引入和使用ehcache。

1、导入pom依赖

pom.xml

		<dependency>
            <groupId>org.ehcache</groupId>
            <artifactId>ehcache</artifactId>
            <version>3.8.1</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mybatis.caches/mybatis-ehcache -->
        <dependency>
            <groupId>org.mybatis.caches</groupId>
            <artifactId>mybatis-ehcache</artifactId>
            <version>1.2.0</version>
        </dependency>

2、配置ehcache

eacache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd">
 <!-- 磁盘保存路径 -->
 <diskStore path="D:\ehcache" />

 <defaultCache 
   maxElementsInMemory="1" 
   maxElementsOnDisk="10000000"
   eternal="false" 
   overflowToDisk="true" 
   timeToIdleSeconds="120"
   timeToLiveSeconds="120" 
   diskExpiryThreadIntervalSeconds="120"
   memoryStoreEvictionPolicy="LRU">
 </defaultCache>
</ehcache>

3、在Mapper代理文件中使用ehcache缓存

EmpDao.xml

<cache type="org.mybatis.caches.ehcache.EhcacheCache"></cache>

补充:只是简单的介绍了第三方缓存的概念和使用。

一般是用不到的,需要用到第三方缓存的时候再详细学习。

总结

在本篇文章中我们就详细学习了Mybatis中的缓存机制。应该掌握如下知识:

  • 掌握Mybatis中缓存概念及分类
  • 掌握一级缓存的基本使用和失效场景
  • 掌握二级缓存的基本配置使用和配置属性
  • 掌握缓存命中率的概念、缓存的查询顺序
  • 了解Mybatis对于第三方缓存的支持

再次贴出官网,要学会从官网学习:https://mybatis.org/mybatis-3/zh/sqlmap-xml.html#cache

猜你喜欢

转载自blog.csdn.net/qq_42583242/article/details/107009380