一、简介
1、场景
由于数据字典的变化不是很频繁,而且系统对数据字典的访问较频繁,所以我们有必要把数据字典的数据存入缓存,减少数据库压力和提高访问速度。这里,我们使用Redis作为系统的分布式缓存中间件。

2、RedisTemplate
在Spring Boot项目中中,默认集成Spring Data Redis,Spring Data Redis针对Redis提供了非常方便的操作模版RedisTemplate,并且可以进行连接池自动管理。
二、引入Redis
1、项目中集成Redis
service-base模块中添加redis依赖,Spring Boot 2.0以上默认通过commons-pool2连接池连接Redis

<!-- spring boot redis缓存引入 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 缓存连接池-->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

<!-- redis 存储 json序列化 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
</dependency>

2、添加Redis连接配置
项目 的 application.yml 中添加如下配置

#spring: 
  redis:
    host: 127.0.0.1
    port: 6379
    database: 0
    password: 123456 #默认为空
    timeout: 3000ms #最大等待时间,超时则抛出异常,否则请求一直等待
    lettuce:
      pool:
        max-active: 20  #最大连接数,负值表示没有限制,默认8
        max-wait: -1    #最大阻塞等待时间,负值表示没限制,默认-1
        max-idle: 8     #最大空闲连接,默认8
        min-idle: 0     #最小空闲连接,默认0

3、启动Redis服务
三、测试RedisTemplate
1、存值测试
test中创建测试类RedisTemplateTests

package com.xiaoliu.system.core;

import com.xiaoliu.system.core.mapper.DictMapper;
import com.xiaoliu.system.core.pojo.entity.Dict;
import com.xiaoliu.system.core.service.DictService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;

/**
 * @description:
 * @author: 刘瑞_LiuRui
 * @data: 2022/12/30
 * @Version 1.0
 **/
@SpringBootTest
@RunWith(SpringRunner.class)
public class RedisTemplateTests {
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private DictMapper dictMapper;
    @Test
    public void saveDict(){
        Dict dict = dictMapper.selectById(1);
        //向数据库中存储string类型的键值对, 过期时间5分钟
        redisTemplate.opsForValue().set("dict", dict, 5, TimeUnit.MINUTES);
        Object dict1 = redisTemplate.opsForValue().get("dict");
        System.out.println(dict1);
    }
}

发现RedisTemplate默认使用了JDK的序列化方式存储了key和value,会导致中文乱码的样子,所以我需要修改序列化方法

没有序列化的样子

spring 读取redis缓存 springdata redis_java

2、Redis配置文件
项目 中添加RedisConfig,我们可以在这个配置文件中配置Redis序列化方案

package com.xiaoliu.system.core.config;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @description:
 * @author: 刘瑞_LiuRui
 * @data: 2022/12/30
 * @Version 1.0
 **/
@Configuration
public class RedisConfig {


    @Bean
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory redisConnectionFactory) {

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);

        //首先解决key的序列化方式,
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringRedisSerializer);

        //解决value的序列化方式
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);

        //序列化时将类的数据类型存入json,以便反序列化的时候转换成正确的类型
        ObjectMapper objectMapper = new ObjectMapper();
        //objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL); // 将当前类的数据类型也存到redis数据库里面,方便以后通过反射机制好还原

        // 解决jackson2无法反序列化LocalDateTime的问题
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); // 先禁用默认的LocalDateTime的日期形式
        objectMapper.registerModule(new JavaTimeModule()); // 在设置 java 模型的样子

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);


        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }
}

序列化后

spring 读取redis缓存 springdata redis_spring 读取redis缓存_02

如何保证缓存与数据库的双写一致性?

• 用户请求过来之后,先查缓存有没有数据,如果有则直接返回。
• 如果缓存没数据,再继续查数据库。
• 如果数据库有数据,则将查询出来的数据,放入缓存中,然后返回该数据。
• 如果数据库也没数据,则直接返回空
如果数据库中的某条数据,放入缓存之后,又立马被更新了,那么该如何更新缓存呢?

答:当然不行,如果不更新缓存,在很长的一段时间内(决定于缓存的过期时间),用户请求从缓存中获取到的都可能是旧值,而非数据库的最新值。这不是有数据不一致的问题?

更新的时候,先更新数据库,然后再删除缓存(避免访问的是修改后的值)