MyBatis缓存详解(一级缓存、二级缓存、缓存查询顺序)

固态硬盘缺陷:无法长时间使用,而磁盘只要不消磁,只要不受到磁影响,就可以长期使用,因此绝大多数企业还是使用磁盘来存储数据

像mysql这种关系型数据库中的数据存储在磁盘中,为方便查询,减少系统开销,提高查询效率,会在内存中开辟空间用于存储经常查询、但不怎么改变的数据,这块空间称为缓存

MyBatis缓存分类

  1. 一级缓存
  2. 二级缓存

前置知识,MyBatis是如何执行的

java 复制代码
private InputStream in = null;
    private SqlSession session = null;
    private UserDao mapper = null;

    @Before  //前置通知, 在方法执行之前执行
    public void init() throws IOException {
        //加载主配置文件,目的是为了构建SqlSessionFactory对象
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //通过SqlSessionFactory工厂对象创建SqlSesssion对象
        session = factory.openSession();
  
        //通过Session创建UserDao接口代理对象
        mapper = session.getMapper(UserDao.class);

    }

    @After  //@After: 后置通知, 在方法执行之后执行 。
    public void destory() throws IOException {
        //释放资源
        session.close();
        in.close();
    }
}

1.一级缓存

默认情况下开启的缓存 放在SqlSession对象中

什么是对象→堆里面的一块内存空间

上述的SqlSession对象也是堆里面的一块空间,SqlSession对象里有一块一级缓存空间(SqlSession对象所在空间∈内存,cache缓存所在空间∈SqlSession对象所在空间,因此,之前说缓存是内存中的一块空间)

两次查询同一个数据,sql语句只会执行一次,第二次会从缓存中直接获取数据

java 复制代码
@Test
    public void findById(){
        User user1 = mapper.findById(1);
        User user2 = mapper.findById(1);
        System.out.println(user1.toString());
        System.out.println(user2.toString());
        System.out.println(user1 == user2);  //在引用数据类型当中 == 是用来比较什么的? 是否指向同一个内存地址
    }
一级缓存失效的四种情况

1.sqlSession不同

一个SqlSession可以生成多个session对象,继而创建多个mapper

java 复制代码
private InputStream in = null;
    private SqlSession session = null;
    private SqlSession session2 = null;
    private UserDao mapper = null;
    private UserDao mapper2 = null;

    @Before  //前置通知, 在方法执行之前执行
    public void init() throws IOException {
        //加载主配置文件,目的是为了构建SqlSessionFactory对象
        in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //创建SqlSessionFactory对象
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
        //通过SqlSessionFactory工厂对象创建SqlSesssion对象
        session = factory.openSession();
        session2 = factory.openSession();
        //通过Session创建UserDao接口代理对象
        mapper = session.getMapper(UserDao.class);
        mapper2 = session2.getMapper(UserDao.class);

    }

    @After  //@After: 后置通知, 在方法执行之后执行 。
    public void destory() throws IOException {
        //释放资源
        session.close();
        in.close();
    }

    /*
     * sqlSession不同
     * 两个mapper不是同一个session生成的,故对应的开辟的内存空间也不同
     * */
    @Test
    public void findById5(){
        User user1 = mapper.findById(1);
        User user2 = mapper2.findById(1);
        System.out.println(user1 == user2);
    }
}

两个mapper不是同一个session生成的,故对应的开辟的内存空间也不同

2.sqlSession相同,查询条件不同,多次查询不同的情况,不会导致缓存失效

其实也很好理解,第一次查询的数据存储在缓存中,但第二次查询查询的是与第一次不同的数据,那缓存中必然是没有第二次查询的数据的

java 复制代码
 /*
    sqlSession相同,查询条件不同
    */
    @Test
    public void findById1(){
        User user1 = mapper.findById(1);
        User user2 = mapper.findById(2);
        System.out.println(user1.toString());
        System.out.println(user2.toString());
        System.out.println(user1 == user2);
    }

3.sqlSession相同,查询的数据也相同,但两次查询之间执行了增删改操作

缓存要和数据库保持一致,如果进行了增删改操作却不修改缓存,那么可能造成缓存和数据库不一致的情况,所以执行增删改操作后缓存便失效了

java 复制代码
/**
     * sqlSession相同,查询的数据也相同,但两次查询之间执行了增删改操作
     * 缓存要和数据库保持一致,如果进行了增删改操作不修改缓存,那么可能造成缓存和数据库不一致的情况!
     */
    @Test
    public void findById2(){
        User user1 = mapper.findById(1);
        mapper.insert(user1); //两次查询之间执行了增删改操作!
        User user2 = mapper.findById(1);
        System.out.println(user1 == user2);
    }

4.sqlSession相同,手动清除一级缓存

java 复制代码
/*
    * sqlSession相同,手动清除一级缓存
     * */
    @Test
    public void findById3(){
        User user1 = mapper.findById(1);
        session.clearCache(); //清除缓存
        User user2 = mapper.findById(1);
        System.out.println(user1 == user2);
    }

session.clearCache()

这个也很好理解,第一次查询后缓存被清除了,第二次查询相同的数据时,缓存中已经没有了相应的数据,必然是要重新执行sql语句进行查询,开辟了新的内存空间,故指向地址也不一致了

2.二级缓存

二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactroy创建SqlSession查询结果会被缓存;此后若再次执行相同的查询语句,结果会从一个缓存中获取。

(1)二级缓存开启的条件

①:在核心配置文件中,设置全局属性caheEnabled="true"

②:在mapper映射文件中配置<cache/>

③:查询数据所转换的实体类类型必须实现序列化接口 implements Serializable

④:二级缓存必须在SqlSession(一级缓存)关闭或提交之后有效

(2)开启二级缓存

①.在SqlMapConfig.xml配置文件中开启二级缓存

xml 复制代码
<!‐‐ 开启二级缓存 ‐‐>
<settings>
    <!--开启二级缓存-->
    <setting name="cacheEnabled" value="true"/>
</settings>

②. 在UserMapper.xml配置文件声明使用二级缓存

xml 复制代码
<!--使用二级缓存-->
    <cache/>

③:查询数据所转换的实体类类型必须实现序列化接口

java 复制代码
public class User  implements Serializable{
    ......
}

④:二级缓存必须在SqlSession关闭或提交之后有效

java 复制代码
@Test
    public void findById4(){
        User user1 = mapper.findById(1);
        //关闭一级缓存
        session.close();
        User user2 = mapper2.findById(1);
        session2.close();
        System.out.println(user1 == user2);   // 一级缓存缓存的是对象,二级缓存缓存的是数据
    }

打印发现2个对象的地址值不一样,但是确实只发送了一次SQL语句的查询,二级缓存中存储的是数据,不是对象

一级缓存缓存的是对象(俩对象地址相同则相等),而二级缓存缓存的是数据,数据无法直接对比是否相同,故为false

(3)二级缓存失效的情况

两次查询之间行了任意的增删改,会使得一级二级缓存同时失效

这点与一级缓存一致,故不再赘述

(4)Catch参数的具体细节

缓存空间也是有限的,当满了之后,再进来新的数据,就要将之前的数据进行移除

eviction(收回策略)

  • LRU(最近最少使用的):移除最长时间不被使用的对象,这是默认值。
  • FIFO(先进先出):按对象进入缓存的顺序来移除它们。
  • SOFT(软引用):移除基于垃圾回收器状态和软引用规则的对象。
  • WEAK(弱引用):更积极地移除基于垃圾收集器状态和弱引用规则的对象。

flushinterval(刷新间隔)

可以被设置为任意的正整数,而且它们代表一个合理的毫秒形式的时间段。

  • 默认情况不设置,即没有刷新间隔,缓存仅仅在调用语句时刷新。

size(引用数目)

  • 可以被设置为任意正整数,要记住缓存的对象数目和运行环境的可用内存资源数目。默认值是1024 。

readOnly(只读)

属性可以被设置为 true / false。

  • true:只读缓存:**会给所有调用者返回缓存对象的相同实例。**因此这些对象不能被修改, 这提供了很重要的性能优势。
  • false读写缓存: 通过序列化返回缓存对象的拷贝,这种方式会慢一些,但是安全,因此默认是 false。

配置结果

复制代码
<cache eviction="FIFO" flushInterval="6000" size="512" readOnly="true"/>   

3.Mybatis缓存查询顺序

  • 先查询二级缓存,因为二级缓存中可能会有其他程序查询出来的数据,可以直接拿来使用
  • 如果二级缓存未命中,再查询一级缓存
  • 如果一级缓存也没有命中,则查询数据库
  • SqlSession关闭之后,一级缓存的数据会写入二级缓存
相关推荐
月疯2 小时前
FLASK与JAVA的文件互传并带参数以及流上传(单文件互传亲测)
java·python·flask
Stream_Silver2 小时前
LangChain入门实践3:PromptTemplate提示词模板详解
java·python·学习·langchain·language model
小树懒(-_-)2 小时前
SEO:Java项
java·开发语言
TeleostNaCl3 小时前
如何在 IDEA 中使用 Proguard 自动混淆 Gradle 编译的Java 项目
android·java·经验分享·kotlin·gradle·intellij-idea
小蕾Java3 小时前
IntelliJ IDEA 2025:最新使用图文教程!
java·ide·intellij-idea
聪明的笨猪猪3 小时前
Java “线程池(1)”面试清单(含超通俗生活案例与深度理解)
java·经验分享·笔记·面试
Miraitowa_cheems3 小时前
LeetCode算法日记 - Day 63: 图像渲染、岛屿数量
java·数据结构·算法·leetcode·决策树·贪心算法·深度优先
karry_k3 小时前
ThreadLocal原理以及内存泄漏
java·后端·面试
羚羊角uou4 小时前
【Linux】POSIX信号量、环形队列、基于环形队列实现生产者消费者模型
java·开发语言
代码萌新知10 小时前
设计模式学习(五)装饰者模式、桥接模式、外观模式
java·学习·设计模式·桥接模式·装饰器模式·外观模式