Spring 全家桶之 Spring Boot 2.6.4( Ⅰ )- Caching(Part C)

2022-09-26 16:01:59 浏览数 (1)


一起养成写作习惯!这是我参与「掘金日新计划 · 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中保存的数据

0 人点赞