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关闭之后,一级缓存的数据会写入二级缓存
相关推荐
雷神乐乐26 分钟前
MyBatis中的${}和#{}区别
数据库·sql·mybatis·javaweb
MZWeiei36 分钟前
实现List接口的三类-ArrayList -Vector -LinkedList
java
怀旧66636 分钟前
Java List 集合
java·数据结构·后端·list·个人开发
听我对云说1 小时前
Java语言程序设计 选填题知识点总结
java·开发语言
苹果酱05671 小时前
浅谈vue3 和 vue2的区别
java·spring boot·毕业设计·layui·课程设计
Allen Bright1 小时前
如何使用Jedis连接Redis
数据库·redis·缓存
forestsea1 小时前
【Java 解释器模式】实现高扩展性的医学专家诊断规则引擎
java·人工智能·设计模式·解释器模式
陈小于1 小时前
springboot集成shiro和前后端分离配置
java·spring boot·后端
不修×蝙蝠2 小时前
数据结构--数组实现栈和队列
java·数据结构·数组··队列
信息化未来2 小时前
odoo17 档案管理之翻译2
java·服务器·前端