缓存
1. 前言
频繁地查询必然会给数据库带来巨大的压力,为此 MyBatis 提供了丰富的缓存功能。缓存可以有效的提升查询效率、缓解数据库压力,提高应用的稳健性。
MyBatis 的缓存有两层,默认情况下会开启一级缓存,并提供了开启二级缓存的配置。本小节我们将一起学习 MyBatis 的缓存,充分地了解和使用它。
2. 一级缓存
MyBatis 一级缓存是默认开启的,缓存的有效范围是一个会话内。一个会话内的 select 查询语句的结果会被缓存起来,当在该会话内调用 update、delete 和 insert 时,会话缓存会被刷新,以前的缓存会失效。
2.1 使用一级缓存
下面,我们以一个简单的例子来看看 MyBatis 的一级缓存是如何工作的。
@SuppressWarnings({"Duplicates"})
public class CacheTest1 {
public static void main(String[] args) throws IOException {
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession session = sqlSessionFactory.openSession();
// 得到 mapper
UserMapper userMapper = session.getMapper(UserMapper.class);
// 查询得到 user1
User user1 = userMapper.selectUserById(1);
System.out.println(user1);
// 查询得到 user2
User user2 = userMapper.selectUserById(1);
// 通过 == 判断 user1 和 user2 是否指向同一内存区间
System.out.println(user1 == user2);
session.commit();
session.close();
}
}
结果:
User{id=1, username='peter-gao', age=180, score=1000} true
在这个例子中,我们连续两次调用了 userMapper 的 selectUserById 方法,但是在程序输出中,user1 和 user2 却指向了同一块内存区域。这就是 MyBatis 缓存的作用,当第二次调用查询时,MyBatis 没有查询数据库而是直接从缓存中拿到了数据。
2.2 弃用一级缓存
2.2.1 select 配置关闭缓存
select 默认会启用一级缓存,我们也可通过配置来关闭掉 select 缓存。
如下,我们通过 flushCache
属性来关闭 select 查询的缓存。
<select id="selectUserById" flushCache="true" parameterType="java.lang.Integer"
resultType="com.imooc.mybatis.model.User">
SELECT * FROM imooc_user WHERE id = #{id}
</select>
再次运行程序,结果如下:
User{id=1, username='peter-gao', age=180, score=1000} false
此时 user1 与 user2 不再指向同一内存区,缓存失效了。
2.2.2 调用 insert、update、delete 刷新缓存
一般情况下,我们都推荐开启 select 的缓存,因为这会节省查询时间。当然在一个会话中,调用 insert、update、delete 语句时,会话中的缓存也会被刷新。
如下:
UserMapper userMapper = session.getMapper(UserMapper.class);
User user1 = userMapper.selectUserById(1);
System.out.println(user1);
User user = new User();
user.setUsername("cache test");
user.setAge(10);
user.setScore(100);
userMapper.insertUser(user);
User user2 = userMapper.selectUserById(1);
System.out.println(user1 == user2);
session.commit();
session.close();
User{id=1, username='peter', age=18, score=100}
false
在第一个查询调用前,我们先进行了一次 insert 操作,此时会刷新缓存,user1 和 user2 又没有指向同一处内存。
3. 二级缓存
MyBatis 二级缓存默认关闭,我们可以通过简单的设置来开启二级缓存。二级缓存的有效范围为一个 SqlSessionFactory 生命周期,绝大多数情况下,应用都会只有一个 SqlSessionFactory,因此我们可以把二级缓存理解为全局缓存。
3.1 全局可用
在 MyBatis 全局配置文件中,即 mybatis-config.xml 文件,二级缓存可由 settings 下的 cacheEnabled 属性开启。如下:
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
当打开 cacheEnabled 属性后,二级缓存全局可用。
TIPS:注意,这里是可用,cacheEnabled 的默认值其实也是 true,即全局可用,由于二级缓存需要对 mapper 配置后才真正生效,简单来说就是双层开关。当将其设置为 false 后,则全局关闭,mapper 中即使配置了,二级缓存也会失效。
3.2 mapper 中开启
3.2.1 xml 开启
在二级缓存全局可用的情况下,mapper 才可通过 cache 配置开启二级缓存。如,在 UserMapper.xml 文件中开启二级缓存:
<cache/>
这种情况下,缓存的行为如下:
- mapper 下的所有 select 语句会被缓存;
- mapper 下的 update,insert,delete 语句会刷新缓存;
- 使用 LRU 算法来回收对象;
- 最大缓存 1024 个对象;
- 缓存可读、可写。
- 缓存不会根据时间来刷新。
cache 提供了诸多属性来修改缓存行为,示例如下:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这个例子下的缓存使用 FIFO 算法来回收对象,并每隔 60 秒刷新一次,最多缓存 512 个对象,且缓存只可读。
cache 有 4 个属性可配置,从而改变缓存的行为。
属性 | 描述 |
---|---|
eviction | 回收策略,默认 LRU,可选择的有 FIFO(先进先出),SOFT(软引用),WEAK(弱引用) |
flushInterval | 刷新时间 |
size | 最多缓存对象数 |
readOnly | 是否只读 |
3.2.2 注解开启
如果你不使用 mapper.xml 文件,也可以使用注解来开启。
如下:
package com.imooc.mybatis.mapper;
import org.apache.ibatis.annotations.CacheNamespace;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.cache.decorators.FifoCache;
@Mapper
@CacheNamespace(
eviction = FifoCache.class,
flushInterval = 60000,
size = 512,
readWrite = false
)
public interface BlogMapper {
}
注解 CacheNamespace 的配置与 xml 配置保持一致,唯一区别在于若使用注解,那么 eviction 属性需直接给出缓存实现类。
3.3 缓存共享
3.3.1 xml 共享
有时候,我们想在不同的 mapper 中共享缓存,为了解决这类问题,MyBatis 提供了 cache-ref 配置。
使用也很简单,如下:
<cache-ref namespace="com.imooc.mybatis.mapper.UserMapper"/>
mapper 由 namespace 来唯一标识,因此只需在另一个 mapper 文件中添加上 cache-ref 配置,并加上相应的 namespace 即可。
这样当前的 mapper 可以共享来自 UserMapper 的缓存。
3.3.2 注解共享
同样的,我们也可以使用注解来共享缓存。
如下:
@CacheNamespaceRef(UserMapper.class)
public interface BlogMapper {
}
这里,BlogMapper 共享了 UserMapper 的缓存。
TIPS: 注意,CacheNamespaceRef 与 CacheNamespace 不能共存,既然选择了共享就不能再独立开辟缓存区了。
4. 小结
- MyBatis 的一级缓存默认可用,有效范围小,不会影响到其它会话,因此无特殊情况,不推荐丢弃一级缓存。
- MyBatis 二级缓存默认使用程序内存缓存,但这显然不够安全,一般情况下我们都推荐使用 Redis 等专业的缓存。