文章目录
前言
本篇记录怎么使用Redis做Mybtais的缓存。
一、二级缓存
MyBatis中的缓存分为一级缓存和二级缓存
- 一级缓存:基于sqlSession的缓存
- 二级缓存:基于多个sqlSession 共享的namspace数据块
通常一个mapper 都一个namespace,所有的相关二级缓存都存在该namespace 数据块下
查询顺序:先去二级缓存,如果二级没有再去一级缓存,一级没有再去数据库
注意:在spring 配置的mybatis 中不存在一级缓存,二级缓存发生增删改,该namespace 下所有缓存数据 立即清空,目的是为了避免有脏数据存在
二、使用步骤
创建项目,导入依赖和基本编码部分不再赘述
1.开启二级缓存
在配置文件yml中加入以下配置
代码如下:
cache-enabled: true #使用缓存
2.编写ApplicationContextHolder
该类主要是为了在spring环境中获取非spring容器管理的bean
package com.lzl.secondcache;
import org.springframework.stereotype.Component;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.data.redis.core.RedisTemplate;
/**
* 在spring中,只要实现或者继承xxAware接口或者类,在实例该对象时,
* 会调用实现xxAware接口的类的方法,把参数传递
*/
/**
* --效率,是成功的核心关键--
* 在spring中获取非spring管理的bean对象
* @Author lzl
* @Date 2023/3/9 08:00
*/
@Component
public class ApplicationContextHolder implements ApplicationContextAware {
//spring容器
private static ApplicationContext applicationContext;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
ApplicationContextHolder.applicationContext = applicationContext;
}
public static RedisTemplate getRedisTemplate(){
return ApplicationContextHolder.applicationContext
.getBean("redisTemplate",RedisTemplate.class);
}
}
3.编写RedisCache二级缓存工具类
package com.lzl.secondcache;
import org.apache.ibatis.cache.Cache;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
import java.util.Set;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* 数据查询顺序:二级缓存 -> 一级缓存 -> 数据库
* 我们在mybatis中指定了二级缓存,在mybatis启动会生成Cache对象,
* 如果在该类使用@Autowired注入RedisTemplate是无法注入的,需要使用spring注入
*/
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/3/9 08:02
*/
public class RedisCache implements Cache{
//RedisTemplate对象
private RedisTemplate redisTemplate;
//id相当于当前sql对应的cache的命名空间 namespace="com.qf.mapper.xxxMapper"
private String id;
//读写锁:多线程中可以共享锁,如果大家都是读操作,提高数据的读的并发能力
//如果有一个人进行了写操作,其他人都不能进行读写操作了
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//获取RedisTemplate对象
public RedisTemplate getRedisTemplate(){
//判断
if(redisTemplate == null){
synchronized (RedisCache.class){
if(redisTemplate == null){
RedisTemplate redisTemplate = ApplicationContextHolder.getRedisTemplate();
//设置key使用string类型的序列化方式
redisTemplate.setKeySerializer(RedisSerializer.string());
return redisTemplate;
}
return this.redisTemplate;
}
}
return redisTemplate;
}
@Override
public ReadWriteLock getReadWriteLock() {
return readWriteLock;
}
//构造器
public RedisCache(String id) {
System.out.println("id:"+id);
this.id = id;
}
//id相当于当前sql对应的cache的命名空间
@Override
public String getId() {
System.out.println("getId:"+id);
return id;
}
/**
* 将结果放入缓存,当访问查询方法时调用,所以这里必须通过getRedisTemplate()方法来获取redisTemplate对象
* @param key -> 命名空间 + sql + 参数 = 组成的字符串
* @param value -> sql查询的结果
*/
@Override
public void putObject(Object key, Object value) {
System.out.println("putObject中的key:"+key);
System.out.println("putObject中的value:"+value);
getRedisTemplate().opsForValue().set(key.toString(),value);
}
/**
* 获取缓存中的数据,当访问查询方法时调用,所以这里必须通过getRedisTemplate()方法来获取redisTemplate对象
* @param key
* @return
*/
@Override
public Object getObject(Object key) {
System.out.println("getObject:"+key);
return getRedisTemplate().opsForValue().get(key.toString());
}
/**
* 从缓存中移除数据,当访问查询方法时调用,所以这里必须通过getRedisTemplate()方法来获取redisTemplate对象
* @param key
* @return
*/
@Override
public Object removeObject(Object key) {
System.out.println("removeObject:"+key);
return getRedisTemplate().delete(key.toString());
}
/**
* 清空缓存
*/
@Override
public void clear() {
System.out.println("clear");
Set keys = getRedisTemplate().keys("*" + id + "*");
System.out.println("清空缓存keys:"+keys);
getRedisTemplate().delete(keys);
}
/**
* 获取缓存数据长度
* @return
*/
@Override
public int getSize() {
Set keys = getRedisTemplate().keys("*" + id + "*");
return keys.size();
}
}
4.在mapper.xml文件中开启全局二级缓存
<!-- 开启二级缓存(全局) -->
<!--
type:使用自定义的对象进行存储
blocking:true 查询时是否阻塞加锁
flushInterval: 毫秒值,缓存多久清空一次
eviction: 缓存失效策略: LRU – 最近最少使用:移除最长时间不被使用的对象。
FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
readOnly:true 只读,不能被修改
size: 1024 缓存的大小
-->
<cache type="com.lzl.secondcache.RedisCache"/>
5.配置RedisTemplate序列化工具类,实体也需要实现序列化接口
package com.lzl.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* --效率,是成功的核心关键--
*
* @Author lzl
* @Date 2023/3/9 08:05
*/
@Configuration
public class RedisConfig {
/**
* springboot 默认帮我们创建的RedisTemplate的key和value的序列化方式是jdk默认的方式,
* 我们有时候手动向redis中添加的数据可能无法被查询解析出来,所以我们需要修改序列化方式
* @param connectionFactory
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// 配置连接工厂
redisTemplate.setConnectionFactory(connectionFactory);
// 使用StringRedisSerializer来序列化和反序列化Redis的key值
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
// 使用Jackson2JsonRedisSerializer来序列化和反序列化Redis的value值
Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
// 配置对象映射器
ObjectMapper objectMapper = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围。ANY指包括private和public修饰符范围
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入类型,类的信息也将添加到json中,这样才可以根据类名反序列化。
objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY);
// 将对象映射器添加到序列化器中
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
// 配置key,value,hashKey,hashValue的序列化方式
redisTemplate.setKeySerializer(stringRedisSerializer);
redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
redisTemplate.setHashKeySerializer(stringRedisSerializer);
redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
return redisTemplate;
}
}
需要注意的是参数列表会报一个无法自动注入的错误,属于正常现象,不影响代码正常运行
实体类需要实现序列化接口如下图所示
三、测试
首先我们先开启Redis,这里开虚拟机太麻烦,我使用了windows版的redis
启动成功
启动项目
启动成功,测试二级缓存的基本思路是,先调用查询的方法,查看redis中是否有缓存数据,再调用删除的方法,再次查看查看redis中的缓存数据是否被删除
先执行查询
查看控制台
我这里之前已经查过一次,所以直接调用了缓存,没有执行sql语句,如果是第一次访问,会先执行sql语句,再建立缓存
执行删除
查看控制台
删除成功!我们再次执行查询
查看控制台:
发现执行了sql语句
再次查询
缓存命中!大功告成!
总结
本篇简单记录一下springboot整合mybatis使用redis做二级缓存