mybatis之一级缓存和二级缓存

缓存:

查询需要连接数据库,非常的耗费资源,将一次查询的结果,暂存在一个可以直接取到的地方,我们将其称之为缓存,当我们需要再次查询相同的数据时,直接走缓存这个过程,就不用走数据库了

缓存的概念:

存在内存中的临时数据,通过将用户经常查询的数据放在缓存[内存]中,用户去查询数据就不用从磁盘上(关系数据库数据文件)查询,从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题

使用缓存的原因:

减少和数据库的交互次数,减少系统开销,提高系统效率

适合缓存的数据:

经常查询并且不太变化的数据是比较适合缓存的

Mybatis缓存:

mybatis包含一个非常强大的查询缓存特性,他可以非常方便地定制和配置缓存,缓存可以极大的提升查询效率,mybatis系统中默认定义了两级缓存:一级缓存和二级缓存默认情况下,只有一级缓存开启{SqlSession级别的缓存,也称为本地缓存}

二级缓存需要手动开启和配置,它是基于namespace级别的缓存,为了提高扩展性,mybatis定义了缓存接口Cache,我们可以通过实现Cache接口来定义二级缓存

一级缓存:

一级缓存也叫本地缓存:SqlSession

与数据库同一次会话期间查询到的数据会放在本地缓存中,在以后的查询工作中,如果需要获取相同的数据,直接从缓存中拿,不用再去查询数据库

举例:

mybatis之一级缓存和二级缓存_第1张图片

关于测试本案例的其他四个文件代码,在这篇文章有,这里就不过多赘述了,不过需要注意的是mybatis-config.xml文件中绑定的接口名不要忘记更换

一级缓存的使用:

在接口中编写方法:

package dao;
import org.apache.ibatis.annotations.Param;
import pojo.Student;
public interface StudentMapper {
    //根据id查询用户
    Student queryStudentById(@Param("id") int id);
}

StudentMapper.xml文件中编写SQL语句:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="dao.StudentMapper">
    <select id="queryStudentById" resultType="Student">
        select *  from student where id=#{id}
    </select>
</mapper>

实体类:

注:实体类所对应的数据表应提前创建好,并插入数据,注意字段一致

package pojo;
import lombok.Data;
@Data
public class Student {
    private int id;
    private String name;
    private  String password;
}

测试类:

使用同一个SqlSession查询同一组数组两次:

package dao.user;
import dao.StudentMapper;
import pojo.Student;
import utils.mybatis_utils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class MyTest{
    @Test
    public void getUserByLimit(){
    	//打开会话
        SqlSession sqlSession= mybatis_utils.getSqlSession();
        StudentMapper studentMapper=sqlSession.getMapper(StudentMapper.class);
        
        //第一次查询id为2的数据
        Student student1=studentMapper.queryStudentById(2);
        System.out.println(student1);
        
        System.out.println("============================");
        
        //第二次查询id为2的数据
        Student student2=studentMapper.queryStudentById(2);
        System.out.println(student2);
        
        //判断student1和student2是否为同一个对象
        System.out.println(student1==student2);
        
        //关闭会话
        sqlSession.close();
    }
}

输出如下:

通过输出的日志文件,我们可以清楚的看到当查询同一个数据两次时,预编译过程只会加载一次,并且通过比较他两是否相等的输出结果,也可以看出,他两确实是连地址都是相同的,那么也足以说明,与数据库同一次会话期间查询到的数据会放在本地缓存中,在以后的查询工作中,如果需要获取相同的数据,直接从缓存中拿,不用再去查询数据库,也就是不需要进行预编译这个过程

mybatis之一级缓存和二级缓存_第2张图片

一级缓存失效的情况:

1:查询不同数据

举例:

修改查询数据为id等于1和id等于2:

Student student1=studentMapper.queryStudentById(2);
System.out.println(student1);
System.out.println("============================");
Student student2=studentMapper.queryStudentById(1);
System.out.println(student2);
System.out.println("student1和student2是否相等?"+(student1==student2));

输出如下:

当两次查询的数据不同时,我们会发现,其预编译过程进行了两次,并且通过比较发现他两并不相等

mybatis之一级缓存和二级缓存_第3张图片

2:增删改操作,可能会改变原来的数据,所以必定会刷新缓存

举例{这里我们以更新操作为例}:

此次我们进行的操作步骤共有3个:

//1:查询id为1的数据
Student student1=studentMapper.queryStudentById(1);
System.out.println(student1);

//2:更新id为2的数据
studentMapper.updateStudent(new Student(2,"ABC","11000"));
System.out.println("============================");

//3:再次查询id为1的数据
Student student2=studentMapper.queryStudentById(1);
System.out.println(student2);

System.out.println("student1和student2是否相等?"+(student1==student2));

输出如下所示:

通过输出结果,我们会发现,在第一次查询完id为1的数据后,我们对id为2的数据进行了更新操作,虽然id为1的数据并没有做任何的改变,但它此时同样预编译了2次,即缓存失效

mybatis之一级缓存和二级缓存_第4张图片

3:查询不同的.xml文件

不同的.xml文件绑定不同的接口,sqlSession.getMapper获取到的值都不相同了,缓存当然失效了

4:手动清理缓存

方法如下:

在第一次查询结束时,调用clearCache方法手动清除缓存

//查询id为1的数据
Student student1=studentMapper.queryStudentById(1);
System.out.println(student1);

//手动清除缓存
sqlSession.clearCache();

System.out.println("============================");

//再次查询id为1的数据
Student student2=studentMapper.queryStudentById(1);
System.out.println(student2);

System.out.println("student1和student2是否相等?"+(student1==student2));

输出如下:

即使我们两次查询的内容都为id等于1,但预编译过程也进行了两次,说明此时一级缓存失效

mybatis之一级缓存和二级缓存_第5张图片

一级缓存默认是开启的,只在一次SqlSession中有效,也就是从开始连接到关闭连接的这个区间段

如下所示:

mybatis之一级缓存和二级缓存_第6张图片

二级缓存:

二级缓存也叫全局缓存,由于一级缓存作用域太低了,所以诞生了二级缓存,它是基于namespace级别的缓存,一个名称空间,对应一个二级缓存

工作机制:

一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中如果当前会话关闭了,这个会话对应的一级缓存就没有了但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中,新的会话查询信息,就可以从二级缓存中获取内容,不同的mapper查出的数据会放在自己对应的缓存(map)中

默认情况下,只启用了本地的会话缓存[一级缓存],它仅仅对一个会话中的数据进行缓存,如果要启用全局的二级缓存,只需要在SQL 映射文件中添加:

方法1:

<cache/>

这个简单语句的效果如下:

1: 映射语句文件中的所有 select 语句的结果将会被缓存
2: 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存
3:	缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存

4: 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
5: 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用

6:缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改

注: 缓存只作用于 cache 标签所在的映射文件中的语句,如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存,你需要在mapper接口上使用 @CacheNamespaceRef 注解指定缓存作用域

方法2:

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

上述两种方法的主要区别在于,使用方式1,如果实体类未实现序列化接口,那么会报错,而第二种方法因为设置了readOnly参数的值,因此不会出现这种情况

二级缓存的使用步骤:

1:开启全局缓存:

官方文档中虽然指出二级缓存是默认开启的,但我们最好在配置文件的settings标签中显式的写出来,原因是为了增强可读性,当别人阅读你的代码时,可以很快的知道二级缓存确实开启了

在这里插入图片描述

在核心配置文件的settings标签中增加:如下代码

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

2:在要使用二级缓存的Mapper.xml文件中开启

方法1:

<cache/>

如果你是用方法1,报错如下:

mybatis之一级缓存和二级缓存_第7张图片

原因:中的readOnly默认为false,而可读写的缓存会通过序列化返回缓存对象的拷贝,此时需要实体类(这里是User)实现Serializable接口或者配置readOnly=true

解决方法:对实体类进行序列化,如下所示

方法1:在实体类中实现序列化接口

implements Serializable

输出如下所示:

mybatis之一级缓存和二级缓存_第8张图片

注:序列化是深拷贝,所以反序列化后的对象和原对象不是同一个对象,故哈希值不同,因此输出false

方法2:在cache标签中,将readOnly参数的值设置为true

<cache readOnly="true"/>

输出如下:

mybatis之一级缓存和二级缓存_第9张图片

方法2:自定义参数

注:使用方法2未报上面那种错误的原因为:在设置参数时,我们将readOnly的值设置为true

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

3:测试类中测试

package dao.user;
import dao.StudentMapper;
import pojo.Student;
import utils.mybatis_utils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

public class MyTest{
    @Test
    public void getUserByLimit(){
    //同时开启两个SqlSession去查询同一个数据
        SqlSession sqlSession1= mybatis_utils.getSqlSession();
        SqlSession sqlSession2= mybatis_utils.getSqlSession();

        StudentMapper studentMapper1=sqlSession1.getMapper(StudentMapper.class);
        StudentMapper studentMapper2=sqlSession2.getMapper(StudentMapper.class);
			
			//使用会话1查询id为1的数据
        Student student1=studentMapper1.queryStudentById(1);
        System.out.println(student1);
        
        //使用会话2查询id为1的数据
        Student student2=studentMapper2.queryStudentById(1);
        System.out.println(student2);

		System.out.println(student1==student2);
			
		//同时关闭两个会话
        sqlSession1.close();
        sqlSession2.close();
    }
}

输出如下:

根据二级缓存的工作机制:

一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中如果当前会话关闭了,这个会话对应的一级缓存就没有了,但是我们想要的是,会话关闭了,一级缓存中的数据被保存到二级缓存中,新的会话查询信息,就可以从二级缓存中获取内容

而我们上述写法会话1和会话2同时打开同时关闭的做法显然不符合二级缓存工作机制。

mybatis之一级缓存和二级缓存_第10张图片

因此,修改如下所示,我们先关闭SqlSession1后再查询数据,最后关闭SqlSession2:

mybatis之一级缓存和二级缓存_第11张图片输出如下:

我们会发现当SqlSession1会话关闭后,当再次查询与会话1查询相同的数据时,并没有进行预编译的过程,原因是:一级缓存中的数据被保存到二级缓存中,新的会话查询信息就可以从二级缓存中获取内容,因此只进行一次预编译,且通过比较发现他两是同一个对象

mybatis之一级缓存和二级缓存_第12张图片

Mybatis缓存原理:

只要开启了二级缓存,在同一个Mapper下就有效,所有的数据都会先放在一级缓存中,只有当会话提交,或者关闭的时候,才会提交到二级缓存中,图解如下所示

mybatis之一级缓存和二级缓存_第13张图片

你可能感兴趣的:(mybatis,mybatis,缓存,java)