Java学习路线:MyBatis(六)缓存机制

目录

一级缓存

二级缓存

开启二级缓存

mapper中开启

全局开启

二级缓存配置

使用二级缓存

题外话


MyBatis 提供了缓存机制,可以提高查询效率减少数据库的访问次数,从而提升应用程序的性能。

通过缓存,MyBatis 可以将查询结果保存到内存中,当相同的查询再次被执行时,可以直接从缓存中获取结果,而不需要再次访问数据库

MyBatis提供了两种缓存机制

一级缓存

一级缓存是 MyBatis 的默认缓存机制:

  • 作用范围:作用于 SqlSession 范围内,即在同一个 SqlSession 中的相同查询将会被缓存。
  • 缓存范围:当相同的 SQL 语句在同一个 SqlSession 中被多次执行且查询条件相同时,MyBatis 会将第一次的查询结果缓存起来,后续相同的查询可以直接从缓存中读取,而无需再次查询数据库。
  • 清空条件:每当 SqlSession 执行 insertupdatedelete 操作时,一级缓存会被清空,以保证数据的实时性。此外,当 SqlSession 关闭时,一级缓存也会被清空

例如:

SqlSession session = sqlSessionFactory.openSession();
try {
    // 第一次查询,数据从数据库中获取
    student s1 = session.selectOne("getStudentById", 1);
    // 第二次查询,数据从一级缓存中获取
    student s2 = session.selectOne("getStudentById", 1);
    System.out.println(s1==s2);
} finally {
    session.close();
}

在上述代码中,会输出true

因为如果s1和s2的查询条件相同且在同一个 SqlSession 中,那么s2的查询结果会直接从缓存中获取,而不会新建一个对象

但是,一级缓存存在一个问题:

假设当前有两个session,分别都有一个自己的一级缓存,而其中一个session对数据库进行了修改,这个时候该数据库的一级缓存发生了更新,但另一个数据库的一级缓存不变,仍然获取的旧版本的数据,此时,两个session的数据出现了不统一的情况

因此,我们需要使用二级缓存来解决这个问题

二级缓存

开启二级缓存

在默认情况下,只使用了一级缓存,因此需要手动开启二级缓存

mapper中开启

在mapper映射文件中添加<cache/>就可以开启二级缓存:

<mapper namespace="com.test.Mapper.TestMapper">
<cache/>

读取数据优先级:
二级缓存>一级缓存>数据库

全局开启

除了mapper中的设置以外,在MyBatis配置文件中也可以设置开启或关闭二级缓存

不同的事,在mapper中开启的二级缓存只作用于当前mapper,而在MyBatis中设置会作用于全局

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

将Value设置为true,则开启了全局二级缓存

二级缓存配置

使用二级缓存需要在cache标签中对缓存进行配置

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

eviction表示缓存回收策略:

  • LRU(Least Recently Used):最近最少使用,移除最久未被使用的对象(默认)
  • FIFO(First In First Out):先进先出,按照缓存项进入缓存的顺序来移除
  • SOFT:软引用,使用 Java 的软引用机制来移除对象,适合内存紧张时自动清理缓存
  • WEAK:弱引用,使用 Java 的弱引用机制

flushInterval表示刷新的间隔,单位为毫秒,MyBatis 会在这个时间间隔后清空缓存内容,强制重新从数据库获取数据。如果不设置该属性,默认情况下缓存不会自动清空。

size表示指定缓存的最大数量。达到该数量时,根据 eviction 策略移除旧数据,以腾出空间存储新的缓存项

readOnly指定缓存是否为只读状态。只读缓存中的数据不能被修改,如果试图修改数据会抛出异常,默认为false

使用二级缓存

public class Main {
    public static void main(String[] args){
        student t;
        try(SqlSession session = MyBatisUtil.getSession(true)){
            TestMapper mapper = session.getMapper(TestMapper.class);
            t=mapper.getStudentById(1);
        }
        try(SqlSession session2 = MyBatisUtil.getSession(true)){
            TestMapper mapper = session2.getMapper(TestMapper.class);
            t=mapper.getStudentById(1);
        }
    }
}

在上述代码中,session1先进行了查询,session1关闭后,将session1中的一级缓存放入二级缓存,这样,在session2再次调用查询方法时,就可以直接从二级缓存中读取

注意,必须在会话关闭后,才会将缓存内容放入二级缓存

假如想针对某个查询方法关闭二级缓存,别的都保持开启,应该怎么操作呢?

可以在对应的查询方法中,添加flushCache=true

    <select id="getStudentById" parameterType="int" resultType="student" flushCache="true">
        select * from student where number = #{number}
    </select>

这样的话,就会查询一次清空一次,二级缓存中就不会保留内容了

同理,一般来说执行insert、delete、update操作后,二级缓存会清空

但如果对于某些insert、delete、update方法,执行后仍想保留二级缓存,应该怎么做呢?

这时就可以将flushCache改为false,就不会清空二级缓存了

<insert id="insertStudent" parameterType="student" flushCache="false">
        insert into student(number,name,gender) values(#{number},#{name},#{gender});
    </insert>

但是,这种操作会存在一些风险,因为数据库已经更新了,但二级缓存中仍然是旧的数据

因此,使用这种方法时必须要确保下一次拿到的数据是最新的才行,否则就会出现一些错误

题外话

虽然缓存机制对性能进行了很大的提升, 但是缓存存在一个问题:当多个CPU在操作自己的缓存时,可能出现多个缓存内容不同步的问题,MyBatis同样可能出现这种问题

例如,当MyBatis在读取数据库时,我们使用idea手动更改数据库的数据,MyBatis中还会继续读原来的内容,因为缓存中的内容并没有发生变化,而数据库的内容已经变化了

解决这个问题有两种方案:

1. 关闭缓存

让MyBatis直接从数据库中读取,这样的话每次取到的数据一定是最新的

但是,这样的读取效率非常低,速度太慢了

2. 实现缓存共用

即所有MyBatis都使用同一个缓存进行读取

今后将会学习的Redis、Ehcache等缓存框架就可以解决这个缓存一致性的问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值