目录
MyBatis 提供了缓存机制,可以提高查询效率和减少数据库的访问次数,从而提升应用程序的性能。
通过缓存,MyBatis 可以将查询结果保存到内存中,当相同的查询再次被执行时,可以直接从缓存中获取结果,而不需要再次访问数据库
MyBatis提供了两种缓存机制
一级缓存
一级缓存是 MyBatis 的默认缓存机制:
- 作用范围:作用于
SqlSession
范围内,即在同一个SqlSession
中的相同查询将会被缓存。 - 缓存范围:当相同的 SQL 语句在同一个
SqlSession
中被多次执行且查询条件相同时,MyBatis 会将第一次的查询结果缓存起来,后续相同的查询可以直接从缓存中读取,而无需再次查询数据库。 - 清空条件:每当
SqlSession
执行insert
、update
或delete
操作时,一级缓存会被清空,以保证数据的实时性。此外,当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等缓存框架就可以解决这个缓存一致性的问题