本文涉及的代码和数据库表均放在github上,地址:[email protected]:forwhildo/blog_source.git
在数据库的一次会话(sqlSession)中,执行多次查询条件完全相同的sql,Mybatis提供一级缓存机制优化,减轻数据库的负载。在一次会话中,用户执行的sql,会先检测缓存是否存在,不在则查询数据库,将请求与结果缓存在在内存中。此内存为本地内存,是共享项目的内存。
mybatis默认开启一级缓存,在springboot的项目中默认使用数据库连接池,每次执行完一条sql,会话就被spring结束,缓存也被清除。因此我们必须开启事务,同一个事务里面的sqlSession才会被缓存。mybatis通过hashmap维护缓存数据。测试结果如:
一级缓存默认作用域为sqlSession,可修改作用域为STATEMENT,STATEMENT对应的是缓存只对当前执行的Statement有效。
在一次会话期间进行了增删改操作,缓存被清空。
启动Session模式的一级缓存作用于一次会话中,当其他会话进行修改数据或者应用部署分布式环境下写操作会引起脏数据,建议使用一级缓存设定为statement。
一级缓存最大数据共享范围为一次SqlSession内部,而多个sqlSession之间共享数据可以通过二级缓存实现。使用二级缓存只需要在xml文件中添加一个cache标签。并且实例化的数据必须支持序列化。如图:
测试得到:
实际上此次测试并未验证会话之间共享,读者可以执行添加接口,启动服务测试。因为这些操作与本文实际讲述内容相关性不高,不再赘述。
结果仍然为一次查询,圈起来的0.0,0.5表示缓存命中率,并且由于作用域的扩大,不开启事务同样可以共享数据。二级缓存,缓存数据以namespace为大key,里面的方法为小key,数据为小key的value。
二级缓存同一级缓存,当任一会话进行写操作,缓存即被清除。由于mybatis缓存默认是存储在应用内存,当架构为分布式时,不同应用的缓存数据不共享,因此我们将缓存数据存储在redis中,将缓存与应用分离,使得分布式应用可以共享缓存。
从缓存中读数据是通过Cache接口的实现类完成,我们可以通过实现Cache接口来修改读取缓存的方式。
选择使用redisTemplete操作redis,要想定制化cache那么需要使用redisTemplete。由于缓存服务是由mybatis提供,那么我们的定制化cache是不通过spring的工厂管理的,因此我们无法注入redisTemplete。所以我们额外使用工具类获取容器。
@Component
public class ApplicationContextUtils implements ApplicationContextAware {
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
public static Object getBean(String beanName){
return applicationContext.getBean(beanName);
}
}
定制化cache
package top.aday.redis_cache.cache;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import top.aday.redis_cache.util.ApplicationContextUtils;
public class RedisCache implements Cache {
private final String id;
public RedisCache(String id){
this.id = id ;
}
//返回缓存空间唯一标识
@Override
public String getId() {
return this.id;
}
@Override
public void putObject(Object key, Object value) {
System.out.println("============key:" + key.toString());
System.out.println("============value:" + value);
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.opsForHash().put(id,key.toString(),value);
}
@Override
public Object getObject(Object key) {
RedisTemplate redisTemplate = getRedisTemplate();
return redisTemplate.opsForHash().get(id,key.toString());
}
@Override
public Object removeObject(Object key) {
/*不会调用*/
return null;
}
@Override
public void clear() {
RedisTemplate redisTemplate = getRedisTemplate();
redisTemplate.delete(id);
}
@Override
public int getSize() {
RedisTemplate redisTemplate = getRedisTemplate();
return redisTemplate.opsForHash().size(id).intValue();
}
private RedisTemplate getRedisTemplate() {
RedisTemplate redisTemplate = (RedisTemplate) ApplicationContextUtils.getBean("redisTemplate");
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
return redisTemplate;
}
}
配置xml文件的cache标签,使用定制化cache
<cache type="top.aday.redis_cache.cache.RedisCache"/>
启动服务测试得到:
再次测试得:
升级成功。