浅谈Mybatis的一级缓存和二级缓存

MyBatis的缓存机制

缓存的引入

当咱们大量执行重复的查询SQL语句的时候,会频繁的和数据库进行通讯,会增长查询时间等影响用户体验的问题,能够经过缓存,以下降网络流量,使网站加载速度更快.java

MyBatis的一级缓存

默认状况下,MyBatis只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。这也就是你们常说的MyBatis一级缓存,一级缓存的做用域是SqlSession。web

第1种状况:同个session进行两次相同查询

代码验证sql

//测试一级缓存
    @Test
    public void testCache() throws IOException { 
 
  
        //加载配置文件
        InputStream rs = Resources.getResourceAsStream("MyBatisConfig.xml");
        //获取工厂建造类
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取工厂对象
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(rs);
        //获取SqlSession对象
        SqlSession sqlSession = build.openSession(true);
        //获得代理的对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        //根据名字查询
        Student stu1 = mapper.findStudentByName("小花花");
        Student stu2 = mapper.findStudentByName("小花花");
        /* 这里相等,说明默认开启一级缓存,就是在同一个sqlSession域中有效,只要查询相同的语句,就是从一级缓存中获取 */
        System.out.println("两次查询结果的学生对象是不是同一个:"+(stu1==stu2));
        //关闭会话
        sqlSession.close();
        rs.close();
    }

执行结果
在这里插入图片描述
在这里插入图片描述
由上面的执行结果得知
MyBatis是默认开启一级缓存的,做用域是同一个SqlSession,在同一个sqlSession中查询相同的sql语句,第一次查询后会将结果对象存放在一级缓存中,在后面查询相同的sql语句的时候会在一级缓存中去获取,不用在与数据库进行通讯,大大下降了查询速度.数据库

第2种状况:同个session进行两次不一样的查询。

** 代码演示**缓存

//测试一级缓存
    @Test
    public void testCache() throws IOException { 
 
  
        //加载配置文件
        InputStream rs = Resources.getResourceAsStream("MyBatisConfig.xml");
        //获取工厂建造类
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取工厂对象
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(rs);
        //获取SqlSession对象
        SqlSession sqlSession = build.openSession(true);
        //获得代理的对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        //根据名字查询
        Student stu1 = mapper.findStudentByName("小花花");
        Student stu2 = mapper.findStudentByName("小付");

        System.out.println("两次查询结果的学生对象是不是同一个:"+(stu1==stu2));
        //关闭会话
        sqlSession.close();
        rs.close();
    }

控制台输出
在这里插入图片描述安全

第3种状况:不一样session,进行相同查询。

** 代码演示**网络

//测试一级缓存
    @Test
    public void testCache() throws IOException { 
 
  
        //加载配置文件
        InputStream rs = Resources.getResourceAsStream("MyBatisConfig.xml");
        //获取工厂建造类
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取工厂对象
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(rs);
        //获取SqlSession对象
        SqlSession sqlSession = build.openSession(true);
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

        //获取SqlSession2对象
        SqlSession sqlSession2 = build.openSession(true);
        StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
        //根据名字查询
        Student stu1 = mapper.findStudentByName("小花花");
        Student stu2 = mapper2.findStudentByName("小花花");

        System.out.println("两次查询结果的学生对象是不是同一个:"+(stu1==stu2));
        //关闭会话
        sqlSession.close();
        rs.close();
    }

控制台输出
在这里插入图片描述session

第4种状况:同个session,查询以后更新数据,再次查询相同的语句。

代码演示app

//测试一级缓存
    @Test
    public void testCache() throws IOException { 
 
  
        //加载配置文件
        InputStream rs = Resources.getResourceAsStream("MyBatisConfig.xml");
        //获取工厂建造类
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取工厂对象
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(rs);
        //获取SqlSession对象
        SqlSession sqlSession = build.openSession(true);
        //获得代理的对象
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        //根据名字查询
        Student stu1 = mapper.findStudentByName("小花花");
        //修改数据
        stu1.setAge(18);
        //执行更新语句
        mapper.updateStudent(stu1);
        //再次查询
        Student stu2 = mapper.findStudentByName("小花花");
        System.out.println("两次查询结果的学生对象是不是同一个:"+(stu1==stu2));
        //关闭会话
        sqlSession.close();
        rs.close();
    }

控制台输出
在这里插入图片描述框架

一级缓存结论

MyBatis一级缓存的运行过程是这样的:执行SQL语句的过程当中,首次执行它时从数据库获取的全部数据会被存储在一段高速缓存中,从此执行这条语句时就会从高速缓存中读取结果,而不是再次查询数据库。MyBatis提供了默认下基于Java HashMap的缓存实现。

重点是要明白:MyBatis执行SQL语句以后,这条语句的执行结果被缓存,之后再执行这条语句的时候,会直接从缓存中拿结果,而不是再次执行SQL。可是一旦执行新增或更新或删除操做,缓存就会被清除。下面将分状况来验证一下。

很明显,以上各类状况验证了一级缓存的概念,在同个SqlSession中,查询语句相同的sql会被缓存,可是一旦执行新增或更新或删除操做,缓存就会被清除。

MyBatis的二级缓存(全局缓存)

引入

MyBatis 一级缓存最大的共享范围就是一个SqlSession内部,那么若是多个 SqlSession 须要共享缓存,则须要开启二级缓存.

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

二级缓存开启条件
二级缓存默认是不开启的,须要手动开启二级缓存,实现二级缓存的时候,MyBatis要求返回的**POJO必须是可序列化的**。开启二级缓存的条件也是比较简单.

在MyBatis核心配置文件中配置开启二级缓存

<!-- 通知 MyBatis 框架开启二级缓存 -->
<settings>
  <setting name="cacheEnabled" value="true"/>
</settings>

在mapper配置文件中开启当前命名空间的查询结果存放在二级缓存中

<!-- 表示DEPT表查询结果保存到二级缓存(共享缓存)

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

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

    默认是 LRU 最近最少回收策略
    flushinterval 缓存刷新间隔,缓存多长时间刷新一次,默认不清空,设置一个毫秒值
    readOnly: 是否只读;true 只读,MyBatis 认为全部从缓存中获取数据的操做都是只读操做,不会修改数据。MyBatis 为了加快获取数据,直接就会将数据在缓存中的引用交给用户。不安全,速度快。读写(默认):MyBatis 以为数据可能会被修改
    size : 缓存存放多少个元素
    type: 指定自定义缓存的全类名(实现Cache 接口便可)
    blocking: 若缓存中找不到对应的key,是否会一直blocking,直到有对应的数据进入缓存。
    我这里就使用默认的,不去设置参数
     -->
    <cache
    eviction="FIFO"
    flushInterval="60000"
    size="512"
    readOnly="true"
    />

实体类对象要Serializable序列化

@Data //这个注解是lombok包下的,能够自动帮咱们添加getset方法等
public class Student implements Serializable { 
 
  
    private Integer sid;
    private String name;
    private Integer age;
}

代码验证

//测试一级缓存
    @Test
    public void testCache() throws IOException { 
 
  
        //加载配置文件
        InputStream rs = Resources.getResourceAsStream("MyBatisConfig.xml");
        //获取工厂建造类
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        //获取工厂对象
        SqlSessionFactory build = sqlSessionFactoryBuilder.build(rs);
        //获取SqlSession对象
        SqlSession sqlSession = build.openSession(true);
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        //根据名字查询
        Student stu1 = mapper.findStudentByName("小花花");
        System.out.println("第一次查出的对象"+stu1);
        //关闭会话后,会将一级缓存的数据保存在二级缓存中
        sqlSession.close();

        //获取SqlSession2对象
        SqlSession sqlSession2 = build.openSession(true);
        StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
        //新的会话查询就会从二级缓存中获取数据
        Student stu2 = mapper2.findStudentByName("小花花");
        System.out.println("第二次查出的对象"+stu1);

        System.out.println("两次查询结果的学生对象是不是同一个:"+(stu1==stu2));
        //关闭会话
        sqlSession2.close();
        rs.close();
    }

控制台输出
在这里插入图片描述

图解查询语句执行流程
在这里插入图片描述