MyBatis 中的缓存机制(一文带你搞懂)

简介

什么是缓存(Cache)

  • 缓存(Cache)就是:存储在内存中的临时数据。

  • 将用户经常查询的数据放在缓存(内存)中,当用户再次查询相同数据时就不用从磁盘上(关系型数据库数据文件)查询了,而是从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

MyBatis中的缓存机制

缓存的作用:通过减少IO的方式,来提高程序的执行效率。

缓存就是指存在内存中的临时数据,使用缓存能够减少和数据库交互的次数,提高效率。将相同查询条件的sql语句执行一遍后得到的结果存在内存或者某种缓存介质中,当下次遇到一模一样的查询sql时候不在执行sql与数据库交互,而是直接从缓存中获取结果,减少服务器的压力;

MyBatis缓存包括什么

MyBatis的缓存包括一级缓存和二级婚车。一级缓存是会话级别的缓存,二级缓存是全局级别的缓存。一级缓存是默认开启的,基于会话的,通过同一个会话的多次查询来提高性能。而二级缓存是可以手动操作的,可以多个会话共享数据,提供了更广法的缓存范围,其原理是利用了对象引用和HashMap来实现缓存数据的存储和快速检索。

注意:缓存只针对DQL语句,也就是缓存机制只对应Select语句。

一级缓存:

  • 一级缓存又称为“本地缓存”:

  • 一级缓存的生命周期与一个SqlSession对象的生命周期相同,当一个SqlSession对象关闭时,与之对应的一级缓存同步清除。

  • 与数据库同一次会话(SqlSession)期间查询到的数据会放在本地缓存中。

  • 以后如果需要获取相同的数据,直接从缓存中获取,不会再去查询数据库。

测试:

@Test
public void test1() throws Exception{
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    //只执行了一条sql语句
    StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
    Student student1 = mapper1.queryStudentById(2);
    System.out.println(student1);
    Student student2 = mapper1.queryStudentById(2);
    System.out.println(student2);
    sqlSession1.close();
}

执行结果:

执行结果只有一条select语句,但是存在两次查询结果,也就是第二次时从缓存里边得到。

一级缓存失效的情况:

  • 一级缓存是默认开启的,我们没办法关闭它。

  • 一级缓存失效情况:没有使用当前的一级缓存,还需要再向数据库中发起一次查询请求。

不同的sqlSession:

package com.atangbiji.dao;
import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MapperTest {
    @Test
    public void TestQueryBlogById2(){
        Blog blog1 = new Blog();
        Blog blog2 = new Blog();
        //1、从SqlSessionFactory中获取SqlSession对象
        try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
            //2、执行SQL语句
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
            System.out.println("第一次查询结果为:" + blog1);
        }
        //1、从SqlSessionFactory中获取SqlSession对象
        try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
            //2、执行SQL语句
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            blog2 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
            System.out.println("第二次查询结果为:" + blog2);
        }
        //3、判断是否为同一个对象
        System.out.println(blog1 == blog2);
    }
}

运行测试程序TestQueryBlogById2,执行结果如下图所示:

观察结果:发现发送了两条SQL语句!

(2)sqlSession相同,查询条件不同

package com.atangbiji.dao;
import com.atangbiji.pojo.Blog;
import com.atangbiji.utils.MyBatisUtils;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;
public class MapperTest {
    @Test
    public void TestQueryBlogById3(){
        //1、从SqlSessionFactory中获取SqlSession对象
        try(SqlSession sqlSession = MyBatisUtils.getSqlSession()) {
            //2、执行SQL语句
            BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
            Blog blog1 = mapper.queryBlogById("4713a190dee54762a0ea07ba59f6ac33");
            System.out.println("第一次查询结果为:" + blog1);
            Blog blog2 = mapper.queryBlogById("7aa61e8d1e2f40db89d47b1b18ba629a");
            System.out.println("第二次查询结果为:" + blog2);
            //3、判断是否为同一个对象
            System.out.println(blog1 == blog2);
        }
    }
}

运行测试程序TestQueryBlogById3,执行结果如下图所示:

观察结果:发现发送了两条SQL语句!

结论:当前缓存中,不存在这个数据

MyBatis二级缓存:

  • 二级缓存也叫“全局缓存”,一级缓存作用域太低,所有产生了二级缓存。

  • 二级缓存是基于命名空间(namespace)级别的缓存。

  • 一个命名空间(XML映射配置文件),对应一个二级缓存。

注:二级缓存只作用于cache 标签所在的XML映射文件中的语句。

一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中,如果当前会话关闭了,该对应的一级缓存就关闭了,此时,一级缓存中的数据就被转到了二级缓存中,新的会话查询信息,就会在二级缓存中获取内容,不同的  mapper  查出的数据会放在自己对应的缓存中

使用二级缓存需要具备以下几个条件:

  1. <setting name="cacheEnabled" value="true"> 这个配置表示启用 MyBatis 的二级缓存功能。默认就是true,⽆需在配置文件设置。

  2. 在需要使⽤⼆级缓存的StudentMapper.xml⽂件中添加配置:<cache/>

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace:Mapper接口的全路径,使其和Mapper对应-->
<mapper namespace="com.yjg.mapper.StudentMapper">
<!--    默认情况下,二级缓存机制是开启的。-->
<!--    只需要在对应的StudentMapper.xml文件中添加以下标签。用来表示”我"使用该二级缓存。-->
<cache/>
<delete id="delete">
        DELETE from student where stu_id=${id}
</delete>
<select id="queryStudentById" parameterType="integer" resultType="Student">
          select * from student where stu_id=${id}
</select>
</mapper>

注:

  • eviction:清除策略,默认为LRU。可用的清除策略有:

  • LRU – 最近最少使用:移除最长时间不被使用的对象。

  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。

  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。

  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

  • flushInterval:刷新间隔,单位(ms)

  • size:存储对象或列表的引用数目(想缓存对象的大小和运行环境中可用的内存资源,默认值是 1024。)

  • readOnly:只读。

  • 只读的缓存会给所有调用者返回缓存对象的相同实例,这些对象不能被修改,性能提升明显。

  • 而可读写的缓存会(通过序列化)返回缓存对象的拷贝,速度上会慢一些,但是更安全。

  • 默认值为false

  1. 使⽤⼆级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接⼝

@Data
public class Student  implements Serializable {
private Integer stuId;
private String stuName;
private Integer stuAge;
private Double stuSalary;
private Date stuBirth;
private Date createTime;
private Integer courseId;
}
  1. SqlSession对象关闭或提交之后,⼀级缓存中的数据才会被写⼊到⼆级缓存当中。此时⼆级缓存才可⽤

@Test
public void test5() throws Exception{
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
//这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。>
        Student student1 = mapper1.queryStudentById(2);
        System.out.println(student1);
//如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。
//这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)
        Student student2 = mapper2.queryStudentById(2);
        System.out.println(student2);
//程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。
        sqlSession1.close();
//程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
        sqlSession2.close();
}

测试:

@Test
public void test5() throws Exception{
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
        SqlSession sqlSession1 = sqlSessionFactory.openSession();
        SqlSession sqlSession2 = sqlSessionFactory.openSession();
        StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
        StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
//这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。>
        Student student1 = mapper1.queryStudentById(2);
        System.out.println(student1);
//如果这里不关闭SqlSession1对象的话,二级缓存中还是没有数据的。
//这行代码执行结束之后,实际上数据会缓存到一级缓存当中。(sqlSession2是一级缓存。)
        Student student2 = mapper2.queryStudentById(2);
        System.out.println(student2);
//程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。
        sqlSession1.close();
//程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
        sqlSession2.close();
}

可以看见执行结果,存在一个Cache Hit Ratio [com.yjg.mapper.StudentMapper]: 0.0,就是因为在StudentMapper.xml文件中加了

可以看到,缓存命中率(Cache Hit Ratio)是0,执行了两个select语句,说明二级缓存没有生效

如果将关闭位置换一下,就可以让二级缓存生效

@Test
public void test5() throws Exception {
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis.xml"));
    SqlSession sqlSession1 = sqlSessionFactory.openSession();
    SqlSession sqlSession2 = sqlSessionFactory.openSession();
    StudentMapper mapper1 = sqlSession1.getMapper(StudentMapper.class);
    StudentMapper mapper2 = sqlSession2.getMapper(StudentMapper.class);
//这行代码执行结束之后,实际上数据是缓存到一级缓存当中了。(sqlSession1是一级缓存。>
    Student student1 = mapper1.queryStudentById(2);
    System.out.println(student1);
//程序执行到这里的时候,会将sqlSession1这个一级缓存中的数据写入到二级缓存当中。
    sqlSession1.close();
//如果一级缓存中没有找到对应的缓存结果,MyBatis 会尝试从二级缓存中查找,发现存在就会使用二级缓存
    Student student2 = mapper2.queryStudentById(2);
    System.out.println(student2);
//程序执行到这里的时候,会将sqlSession2这个一级缓存中的数据写入到二级缓存当中。
    sqlSession2.close();
}

总结这段代码

这段代码演示了一级缓存和二级缓存的使用。首次查询时会先从数据库查询并将结果放入一级缓存,在关闭 SqlSession 前会将一级缓存数据写入二级缓存。再次查询时,如果一级缓存没有找到结果,则会尝试从二级缓存中获取结果,避免了再次访问数据库

结论:

  • 只要开启了二级缓存,我们在同一个Mapper中的查询,可以在二级缓存中拿到数据。

  • 查出的数据都会被默认先放在一级缓存中。

  • 只有会话提交或者关闭时,一级缓存中的数据才会转到二级缓存中。

MyBatis缓存的原理:

     注:喜欢的朋友可以关注公众号“JAVA学习课堂”方便阅读,内容更丰富哦。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值