Mybatis缓存原理和分级缓存

本文探讨了缓存的基本概念和作用,详细介绍了Mybatis的一级缓存和二级缓存。Mybatis的一级缓存默认开启,作用于SqlSession生命周期内,而二级缓存需要手动配置,作用于Mapper级别,可自定义实现缓存接口。文中还通过示例代码展示了缓存的使用和失效情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

为什么需要缓存

  • 查询数据库需要消耗资源
  • 1次查询的结果,放到可以取到的地方,也就是内存
  • 我们再次查询,就不用走缓存,就直接走数据库了

缓存是什么

  • 放到内存中的临时数据
  • 在没有缓存的时候,随着用户的增加,肯定需要加服务器,但随着服务器增加会出现读写问题,也就是并发问题
  • 第1次打开网页的时候需要加载很长时间,是需要进行数据库操作
  • 读是走缓存的,写不走缓存,读写分离
  • 读写分离体现在不同服务器有不同的职能,比如3台服务器写,1台负责读,因为写比较慢,需要分配多态服务器
  • 还有1个主从复制的概念,又叫做哨兵,学redis数据库的时候可以体会到,写的操作完成之后,立刻进行刷新读的操作

缓存的作用是什么

  • 减少和数据库交互次数,减少系统开销,提高系统效率
  • 什么样的数据适合缓存,经常查询但不经常改变的数据,大部分网页数据都在进行查操作
  • 什么样的数据不适合缓存,不经常查询且经常改变的数据,比如视频下面显示的当前正在观看的人数

Mybatis缓存

  • Mybatis可以方便的配置和定制缓存
  • Mybatis系统默认配置了二级缓存:一级缓存和二级缓存
  • 默认情况下,只有一级缓存开启,sqlSession开启和close之间有效,比如在sqlSession开启和关闭之间查询之间查询同一用户2次,但此时sql只走了1次,并且查询出来的2个对象是相等的
  • 二级缓存需要手动开启配置,namespace级别的缓存,1个namespace对应1个Mapper接口,所以说在整个Mapper.xml文件下都有效
  • 为了提高扩展性,Mybatis定义了缓存接口Cache,可以实现Cache接口来定义二级缓存

小结

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

一级缓存示例代码

下面是根据id查询用户的例子

public class UnitTest {
    @Test
    public void queryUserByIdTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        User user = mapper.queryUserById(1);
        System.out.println(user);
        //查询相同用户
        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
        //第二次查询的时候sql从缓存里面拿

        sqlSession.close();
    }
}

再把修改操作加到两个查询中间

public class UnitTest {
    @Test
    public void queryUserByIdTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //查询用户
        User user = mapper.queryUserById(1);
        System.out.println(user);
        //修改用户
        mapper.updateUser(new User(6,"Tom","Tom404@123.it"));
        //查询相同用户
        User user1 = mapper.queryUserById(1);
        System.out.println(user1);
        //第二次查询的时候sql从缓存里面拿

        sqlSession.close();
    }
}

测试需要,先开启日志,结果出现了3次Preparing,说明sql在第二次查询的时候并没有走缓存,因为增删改查语句会修改修改表数据,再次进行查询会出现不同的结果

[com.bkms.dao.UserMapper.queryUserById]-==>  Preparing: select * from info where id = ?; 
[com.bkms.dao.UserMapper.queryUserById]-==> Parameters: 1(Integer)
[com.bkms.dao.UserMapper.queryUserById]-<==      Total: 1
User(id=1, name=John, email=John123@163.com)
[com.bkms.dao.UserMapper.updateUser]-==>  Preparing: update info set name=?,email=? where id=? 
[com.bkms.dao.UserMapper.updateUser]-==> Parameters: Tom(String), Tom404@123.it(String), 6(Integer)
[com.bkms.dao.UserMapper.updateUser]-<==    Updates: 1
[com.bkms.dao.UserMapper.queryUserById]-==>  Preparing: select * from info where id = ?; 
[com.bkms.dao.UserMapper.queryUserById]-==> Parameters: 1(Integer)
[com.bkms.dao.UserMapper.queryUserById]-<==      Total: 1
User(id=1, name=John, email=John123@163.com)

缓存失效的情况

  • 查询不同的东西
  • 存在增删改查操作
  • 手动删除
sqlSession.clearCache();
  • 查询不同的Mapper.xml

二级缓存示例代码

  1. 全局开启缓存,二级缓存会在1个Mapper.xml文件中全部生效
    <settings>
        <setting name="logImpl" value="LOG4J" />
        <setting name="cacheEnabled" value="true" />
    </settings>
  1. 在Mapper.xml中开启二级缓存
    加cache标签,就开启了二级缓存
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.bkms.dao.UserMapper">

    <!--开启二级缓存-->
    <cache/>

    <!--根据id查询用户-->
    <select id="queryUserById" resultType="User">
        select * from info where id = #{id};
    </select>

    <!--修改用户-->
    <update id="updateUser" parameterType="user">
        update info set name=#{name},email=#{email} where id=#{id}
    </update>
</mapper>

下面这个例子,这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会出现线程安全问题

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>
  1. 测试二级缓存

会话关闭时意味着一级缓存死亡,一级缓存的数据会被保存到二级缓存中,只有当会话提交或关闭时才会提交到二级缓存

    @Test
    public void queryUserByIdTest(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        SqlSession sqlSession2 = MybatisUtils.getSqlSession();
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        //查询用户
        User user = mapper.queryUserById(1);
        System.out.println(user);
        sqlSession.close();
        //程序运行到此处一级缓存死亡

        UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
        //查询相同用户
        User user1 = mapper2.queryUserById(1);
        System.out.println(user1);
        System.out.println(user==user1);//true
        sqlSession2.close();
    }
  1. cache标签属性注意点

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

muskfans

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值