MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。其内置了一个缓存机制,我们查询时,如果缓存中存在数据,那么我们就可以直接从缓存中获取,而不是再去向数据库进行请求。
Mybatis存在一级缓存和二级缓存:
我们首先来看一下一级缓存,默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存(一级缓存无法关闭,只能调整),让我们如使用:
public static void main(String[] args) throws InterruptedException {
try (SqlSession sqlSession = MybatisUtil.getSession(true)){
TestMapper testMapper = sqlSession.getMapper(TestMapper.class);
Student student1 = testMapper.getStudentBySid(1);
Student student2 = testMapper.getStudentBySid(1);
System.out.println(student1 == student2);
}
}
会发现结果为true,因为处于一个会话中,mybatis动用了缓存,就不用去重新构造对象了。而一级缓存在我们进行增删改操作后就会失效,保证数据为最新内容且在会话结束后,会清除所有缓存。但需要注意的是,一级缓存只针对单个会话,多个会话之间不相通的。
值得注意的是,一个会话DML操作只会重置当前会话的缓存,不会重置其他会话的缓存,也就是说,其他会话缓存是不会更新的!
一级缓存给我们提供了很高速的访问效率,但是它的作用范围实在是有限,如果一个会话结束,那么之前的缓存就全部失效了,但是我们希望缓存能够扩展到所有会话都能使用,因此我们可以通过二级缓存来实现,二级缓存默认是关闭状态,要开启二级缓存,我们需要在映射器(mapper)XML文件中添加:
<cache/>
可见二级缓存是Mapper级别的,也就是说,当一个会话失效时,它的缓存依然会存在于二级缓存中,因此如果我们再次创建一个新的会话会直接使用之前的缓存,我们首先根据官方文档进行一些配置:
<cache
eviction="FIFO"
flushInterval="60000"
size="512"
readOnly="true"/>
这样我们就能实现跨会话的缓存操作。
如果我们不希望某个方法开启缓存,可以添加useCache属性来关闭缓存:
<select id="getStudentBySid" resultType="Student" useCache="false">
select * from student where sid = #{sid}
</select>
我们也可以使用flushCache=”false”在每次执行后都清空缓存,通过这这个我们还可以控制DML操作完成之后不清空缓存。
<select id="getStudentBySid" resultType="Student" flushCache="true">
select * from student where sid = #{sid}
</select>
添加了二级缓存之后,会先从二级缓存中查找数据,当二级缓存中没有时,才会从一级缓存中获取,当一级缓存中都还没有数据时,才会请求数据库。
前面中,我们理解的Mybatis二级缓存的使用,但实际上,Mybatis原生的二级缓存是存在在单个虚拟机上的。如果多个服务器访问同一个数据库,二级缓存只会在各自的服务器上生效。如果我们需要服务共用缓存,就需要使用分布式缓存来实现。
我们使用Redis作为缓存数据库,首先需要手动实现Mybatis提供的Cache接口:
//实现Mybatis的Cache接口
public class RedisMybatisCache implements Cache {
private final String id;
private static RedisTemplate<Object, Object> template;
//注意构造方法必须带一个String类型的参数接收id
public RedisMybatisCache(String id){
this.id = id;
}
//初始化时通过配置类将RedisTemplate给过来
public static void setTemplate(RedisTemplate<Object, Object> template) {
RedisMybatisCache.template = template;
}
@Override
public String getId() {
return id;
}
@Override
public void putObject(Object o, Object o1) {
//这里直接向Redis数据库中丢数据即可,o就是Key,o1就是Value,60秒为过期时间
template.opsForValue().set(o, o1, 60, TimeUnit.SECONDS);
}
@Override
public Object getObject(Object o) {
//这里根据Key直接从Redis数据库中获取值即可
return template.opsForValue().get(o);
}
@Override
public Object removeObject(Object o) {
//根据Key删除
return template.delete(o);
}
@Override
public void clear() {
//由于template中没封装清除操作,只能通过connection来执行
template.execute((RedisCallback<Void>) connection -> {
//通过connection对象执行清空操作
connection.flushDb();
return null;
});
}
@Override
public int getSize() {
//这里也是使用connection对象来获取当前的Key数量
return template.execute(RedisServerCommands::dbSize).intValue();
}
}
之后我们需要去编写配置类:
@Configuration
public class MainConfiguration {
@Resource
RedisTemplate<Object, Object> template;
@PostConstruct
public void init(){
//把RedisTemplate给到RedisMybatisCache
RedisMybatisCache.setTemplate(template);
}
}
最后我们在Mapper上启用此缓存即可:
//只需要修改缓存实现类implementation为我们的RedisMybatisCache即可
@CacheNamespace(implementation = RedisMybatisCache.class)
@Mapper
public interface MainMapper {
@Select("select name from student where sid = 1")
String getSid();
}
这样我们就实现了Mybatis二级缓存的分布式方案。