一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第27天,点击查看活动详情。
二、Redis
将方法查询结果保存在ConcurrentMap中是一种临时性的方案,一旦应用重启所有的缓存全部被清除了,所以最好还是使用EhCache、Redis中间件用作缓存,可以将缓存进行持久化
整合Redis
Spring Boot整合Redis只需要在pom文件中添加redis-starter
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
配置Redis
代码语言:javascript复制# 默认就是使用localhost
spring:
redis:
port: 6379
host: localhost
password: 12345
database: 0
如果使用本地安装的redis,端口为6379并且没有密码,可以不用配置连接信息,直接使用默认的配置即可
Redis自动配置类是RedisAutoConfiguration
该类往容器中添加了两个组件RedisTemplate和StringRedisTemplate,其中RedisTemplate是用于操作K、V都是字符串的数据,RedisTemplate用于操作K、V都是对象的数据
操作 String 类型数据
在test包下新建测试类StringRedisTemplateTest,导入StringRedisTemplate
代码语言:javascript复制@SpringBootTest
public class StringRedisTemplateTest {
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Test
public void testValue(){
stringRedisTemplate.opsForValue().append("hello","thor");
String hero = stringRedisTemplate.opsForValue().get("hello");
System.out.println("读取到的数据为:" hero);
}
}
通过opsForValue()的append()方法和get()方法可以往redis中添加和读取数据
操作 List 类型数据
新增一个测试方法testList(),操作List类型数据
代码语言:javascript复制@Test
public void testList(){
stringRedisTemplate.opsForList().leftPush("heros","thor");
stringRedisTemplate.opsForList().rightPushAll("heros","banner", "stark", "clint");
String hero = stringRedisTemplate.opsForList().index("heros",2);
System.out.println("索引为2位置的元素为:" hero);
System.out.println("左侧弹出一个元素" stringRedisTemplate.opsForList().leftPop("heros"));
}
redisTemplate 操作对象
新建一个测试类RedisTemplateTest
代码语言:javascript复制@SpringBootTest
public class RedisTemplateTest {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private TeslaService teslaService;
@Test
public void testObject(){
Tesla tesla = teslaService.getTeslaById(1166057546);
redisTemplate.opsForValue().set("1166057546",tesla);
// 获取存储的对象
Tesla teslaAtRedis = (Tesla) redisTemplate.opsForValue().get("1166057546");
System.out.println("Redis中存储的对象:" teslaAtRedis);
}
}
这是因为没有实现序列化接口导致的报错
使entity包下的Tesla实体类和Factory实体类实现序列化Serializable接口
再次测试
查看Redis中存储的数据
默认保存对象,使用jdk序列化机制,序列化的数据保存到redis中,如果想要保存json格式的数据就需要自定义RedisTemplate,使用指定的序列化规则
将LilithCacheKeyGeneartor重命名为LilithConfig,作为自定义主配置类,增加自定义的RedisTemplate
代码语言:javascript复制@Bean
public RedisTemplate<Object, Tesla> teslaRedisTemplate(RedisConnectionFactory factory){
RedisTemplate<Object, Tesla> teslaRedisTemplate = new RedisTemplate<>();
teslaRedisTemplate.setConnectionFactory(factory);
teslaRedisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(Tesla.class));
return teslaRedisTemplate;
}
Jackson2JsonRedisSerializer序列化器可以在redis中保存Json格式数据
代码语言:javascript复制@Autowired
private RedisTemplate<Object, Tesla> teslaRedisTemplate;
@Test
public void testObjectByTeslaRedisTemplate(){
Tesla tesla = teslaService.getTeslaById(1166057547);
teslaRedisTemplate.opsForValue().set("1166057547", tesla);
// 获取存储的对象
Tesla teslaAtRedis = teslaRedisTemplate.opsForValue().get("tesla");
System.out.println("Redis中存储的对象:" teslaAtRedis);
}
再次查看redis中保存的数据的格式已经变为json格式
三、Redis整合CacheManager
引入redis的starter后,在application.yml配置文件中开启debu=true,查看控制台输出内种中有哪些组件匹配上了
注意有两个RedisCacheConfiguration,这里匹配上的是autoconfigure.cache
包下的RedisCacheConfiguration
RedisCacheConfiguration配置类往容器中添加了一个RedisCacheManager缓存管理器,并且容器中存在RedisConnectionFactory的前提下才会导入RedisCacheManager
RedisCacheManager通过loadCaches()方法来获取缓存
如果不存在就通过createRedisCache()方法来创建缓存
RedisCacheManager可以创建RedisCache来作为缓存组件,RedisCache通过操作redis缓存数据。
使用 RedisCacheManager
启动应用,在浏览器查询 /tesla/1166057546
查询多次,只会执行一次SQL,第一次执行时数据已被缓存在Redis中,后续通过读取Redis获得查询结果,重启项目,再次查询相同的数据
仍然不会执行SQL语句,数据已经缓存在redis中,不会像存储在CurrentMap中会随着项目重启清空缓存数据
查看Redis中保存的数据
这里存储的格式是按照默认的jdk序列化保存的,如果想要保存json格式的数据需要自定义CacheManager,指定序列化规则
自定义一个cacheManager
使用RedisCacheManagerBuilder的builder()方法创建RedisCacheManager
代码语言:javascript复制@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory).cacheDefaults(this.determineConfiguration(resourceLoader.getClassLoader()));
return builder.build();
}
// RedisCacheManager配置类
private RedisCacheConfiguration determineConfiguration(ClassLoader classLoader) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig();
config = config.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new Jackson2JsonRedisSerializer<Tesla>(Tesla.class)));
return config;
}
清空redis缓存,重启应用,再次查询 /tesla/1166057546
查看redis中存储的数据格式
已经变为JSON格式,但是这里的key相比使用ConcurrentMap存储结果时,key默认是目标方法传递的参数,这里多了一个前缀
这是因为默认的RedisCacheConfiguration配置类前缀
加前缀是否了防止key被覆盖,比如存储的Tesla的key为1,存储的Factory的key也为1,就会导致数据混乱。
可以调用这个方法来去除前缀或者其他方法来自定义前缀,参考org.springframework.data.redis.cache.RedisCacheConfiguration类中的其他方法
controller包下增加FactoryController
代码语言:javascript复制@RestController
public class FactoryController {
@Autowired
private FactoryService factoryService;
@GetMapping("/factory/{id}")
public Factory find(@PathVariable("id") Integer id){
return factoryService.getFactoryById(id);
}
}
service包下增加FactoryService接口和impl包下的FactoryServiceImpl
代码语言:javascript复制public interface FactoryService {
Factory getFactoryById(Integer id);
}
代码语言:javascript复制@Service
@Slf4j
public class FactoryServiceImpl implements FactoryService {
@Autowired
private FactoryMapper factoryMapper;
@Override
@Cacheable(cacheNames = "factory")
public Factory getFactoryById(Integer id) {
log.info("查询" id "号超级工厂");
return factoryMapper.selectOneById(id);
}
}
重新启动启动应用,在浏览器查询 /factory/1
控制打印出了一次SQL语句
清空控制台日志,再次查询
之前自定义的CacheManager用来处理Tesla,这个CacheManager管理所有的Cache,所以这里会出现将缓存中查到的Factory对象转换成Tesla对象,转换失败导致报错。
如何解决?在LilithConfig配置类中新增一个FactoryCacheManager,增加@Bean注解,专门处理Factory的序列化
代码语言:javascript复制@Bean
public RedisCacheManager factoryCacheManager(RedisConnectionFactory redisConnectionFactory, ResourceLoader resourceLoader) {
RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(redisConnectionFactory)
.cacheDefaults(this.redisCacheConfiguration(
resourceLoader.getClassLoader())
.serializeValuesWith(RedisSerializationContext
.SerializationPair
.fromSerializer(new Jackson2JsonRedisSerializer<>(Factory.class))));
RedisCacheManager redisCacheManager = builder.build();
return redisCacheManager;
}
给service类上指定CacheManager
代码语言:javascript复制@CacheConfig(cacheManager = "cacheManager")
public class TeslaServiceImpl implements TeslaService {
//中间内容保持不变
}
代码语言:javascript复制@CacheConfig(cacheManager = "factoryCacheManager")
public class FactoryServiceImpl implements FactoryService {
//中间内容保持不变
}
重新启动该应用,再次查询 /factory/1
页面正常,数据也是从缓存中获取的,redis中保存的数据也是JSON格式的
查询Tesla也是正常的
上面都是通过注解来操作缓存,如果想要在代码中操作缓存,就需要通过cacheManager直接在代码中获取缓存,修改缓存
在FactoryController、FactoryService和FactoryServiceImpl中增加方法
代码语言:javascript复制@GetMapping("/factory/save/{id}")
public Factory save(@PathVariable("id") Integer id){
return factoryService.opCache(id);
}
代码语言:javascript复制Factory opCache(Integer id);
代码语言:javascript复制@Autowired
private RedisCacheManager factoryCacheManager;
public Factory opCache(Integer id){
log.info("手动将查询结果存入缓存,存入" id "号超级工厂");
Factory factory = factoryMapper.selectOneById(id);
Cache cache = factoryCacheManager.getCache("2");
cache.put("2", cache);
return factory;
}
重新启动,在浏览器中输入 /factory/save/2
查看redis中保存的数据