前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Spring 全家桶之 Spring Boot 2.6.4( Ⅰ )- Caching(Part C)

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

作者头像
RiemannHypothesis
发布2022-09-26 16:01:59
2820
发布2022-09-26 16:01:59
举报
文章被收录于专栏:Elixir

一起养成写作习惯!这是我参与「掘金日新计划 · 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
image.png
image.png

如果使用本地安装的redis,端口为6379并且没有密码,可以不用配置连接信息,直接使用默认的配置即可

Redis自动配置类是RedisAutoConfiguration

image.png
image.png

该类往容器中添加了两个组件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);
    }

}
image.png
image.png

通过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"));
}
image.png
image.png

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);
    }
}
image.png
image.png

这是因为没有实现序列化接口导致的报错

使entity包下的Tesla实体类和Factory实体类实现序列化Serializable接口

再次测试

image.png
image.png

查看Redis中存储的数据

image.png
image.png

默认保存对象,使用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格式数据

image.png
image.png
代码语言: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);
}
image.png
image.png

再次查看redis中保存的数据的格式已经变为json格式

image.png
image.png

三、Redis整合CacheManager

引入redis的starter后,在application.yml配置文件中开启debu=true,查看控制台输出内种中有哪些组件匹配上了

image.png
image.png

注意有两个RedisCacheConfiguration,这里匹配上的是autoconfigure.cache包下的RedisCacheConfiguration

RedisCacheConfiguration配置类往容器中添加了一个RedisCacheManager缓存管理器,并且容器中存在RedisConnectionFactory的前提下才会导入RedisCacheManager

image.png
image.png

RedisCacheManager通过loadCaches()方法来获取缓存

image.png
image.png

如果不存在就通过createRedisCache()方法来创建缓存

image.png
image.png

RedisCacheManager可以创建RedisCache来作为缓存组件,RedisCache通过操作redis缓存数据。

使用 RedisCacheManager

启动应用,在浏览器查询 /tesla/1166057546

image.png
image.png

查询多次,只会执行一次SQL,第一次执行时数据已被缓存在Redis中,后续通过读取Redis获得查询结果,重启项目,再次查询相同的数据

image.png
image.png

仍然不会执行SQL语句,数据已经缓存在redis中,不会像存储在CurrentMap中会随着项目重启清空缓存数据

查看Redis中保存的数据

image.png
image.png

这里存储的格式是按照默认的jdk序列化保存的,如果想要保存json格式的数据需要自定义CacheManager,指定序列化规则

自定义一个cacheManager

image.png
image.png

使用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

image.png
image.png

查看redis中存储的数据格式

image.png
image.png

已经变为JSON格式,但是这里的key相比使用ConcurrentMap存储结果时,key默认是目标方法传递的参数,这里多了一个前缀

这是因为默认的RedisCacheConfiguration配置类前缀

image.png
image.png
image.png
image.png

加前缀是否了防止key被覆盖,比如存储的Tesla的key为1,存储的Factory的key也为1,就会导致数据混乱。

可以调用这个方法来去除前缀或者其他方法来自定义前缀,参考org.springframework.data.redis.cache.RedisCacheConfiguration类中的其他方法

image.png
image.png

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

image.png
image.png

控制打印出了一次SQL语句

image.png
image.png

清空控制台日志,再次查询

image.png
image.png

之前自定义的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

image.png
image.png

页面正常,数据也是从缓存中获取的,redis中保存的数据也是JSON格式的

image.png
image.png

查询Tesla也是正常的

image.png
image.png

上面都是通过注解来操作缓存,如果想要在代码中操作缓存,就需要通过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

image.png
image.png

查看redis中保存的数据

image.png
image.png
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 二、Redis
    • 整合Redis
      • 配置Redis
    • 操作 String 类型数据
      • 操作 List 类型数据
        • redisTemplate 操作对象
        • 三、Redis整合CacheManager
          • 使用 RedisCacheManager
            • 自定义一个cacheManager
            相关产品与服务
            云数据库 Redis
            腾讯云数据库 Redis(TencentDB for Redis)是腾讯云打造的兼容 Redis 协议的缓存和存储服务。丰富的数据结构能帮助您完成不同类型的业务场景开发。支持主从热备,提供自动容灾切换、数据备份、故障迁移、实例监控、在线扩容、数据回档等全套的数据库服务。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档