Springboot整合Redis(RedisConfig等工具类编写)

我们使用的是上一期创建的Spring boot项目,没看过那篇文章的可以去看看Springboot整合数据库 +JpaRepository实现简单数据查询

目录

  • Redis介绍
  • 1.添加依赖
  • 2.在`application.yml`配置Redis
  • 3.在项目中直接引入Redis的问题记录
  • 4.使用注解来实现redis缓存
    • @Cacheable
    • @CacheEvict
    • @CachePut
    • @Caching
  • 5.编写RedisConfig
    • 定义key的序列化与反序列化
    • 定义value的序列化与反序列化
    • RedisConfig的编写
  • 后记
    • 若有错误,欢迎指正,互相学习,相互交流

Redis介绍

Redis 是目前业界使用最广泛的内存数据存储。相比 Memcached,Redis 支持更丰富的数据结构,例如 hashes, lists, sets 等,同时支持数据持久化。

本文介绍 Spring boot 集成 Redis 来实现数据缓存

1.添加依赖

pom文件中添加redis常用依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

2.在application.yml配置Redis

  redis:
    #数据库索引
    database: 0
    #服务器地址
    host: 127.0.0.1
    #端口号
    port: 6379
    #redis密码(默认为空)
    password:
    #连接redis超时时间
    timeout: 3000

3.在项目中直接引入Redis的问题记录

application.yml文件中配置了redis相关配置了之后呢,我们就可以在使用的类上直接引入RedisTemplate,具体怎么引入看下述代码

	@Autowired
    private RedisTemplate<Object,Object> redisTemplate;

我这里就在Service实现类上引入了RedisTemplate
Springboot整合Redis(RedisConfig等工具类编写)_第1张图片
第一个问题:存进valueObject,必须序列化要不然会报错
在这里插入图片描述
序列化的话在User类上实现Serializable接口即可,看下图
Springboot整合Redis(RedisConfig等工具类编写)_第2张图片
第二个问题:存进rediskey也需要序列化增加其可读性,我们先来看看不序列化的key
Springboot整合Redis(RedisConfig等工具类编写)_第3张图片
上述的代码执行步骤呢就是先去数据库查询名称为userInfoKeykey,然后如果查到了直接返回结果即可,没查到就去数据库查询,并把查询到的结果放到缓存里,再返回结果

执行完上面的步骤,在redis里的key应该是userInfoKey,但是并不是这样的,我们去redis里看看

在这里插入图片描述
我框出来的这段就是我们存进redis的key可以观察到,它是一个序列化后的字符串,这样当key比较多的时候,是非常不方便我们观察的,所以我们在往redis里插入数据时,就得设置key的序列化对象为字符串,加上下面这段代码即可

redisTemplate.setKeySerializer(new StringRedisSerializer());

Springboot整合Redis(RedisConfig等工具类编写)_第4张图片
将redis现在的key删除,执行完上面的代码,我们再来看看redis中的key是怎么样的
Springboot整合Redis(RedisConfig等工具类编写)_第5张图片
所以,当我们设置了key的序列化属性后,存进redis里的就是我们设置的key,在数据库中也更方便我们的观察

第三个问题:所生成的key必须是不同的查询返回的结果所对应的key必须是不同的,如果像我们上面那样写出来的代码,无论我查询name = "张三"还是name = "李四"返回的结果是一样的,因为我们key是写死的,所以针对不同的查询,我们必须生成不同的key,例如根据类名称、方法名称、包名称和方法参数来生成一个唯一的key,这样就可以根据不同的查询所生成对应的key去缓存查询不同的结果

上面说了这么多,其实直接使用RedisTemplate还是有很多问题的,一般我们都会使用注解的方式+RedisConfig 来定义贴合我们项目的缓存


4.使用注解来实现redis缓存

针对上面的问题,spring boot引入的redis的包,里面包含了有关redis的相关注解,我们来看看

@Cacheable

Springboot整合Redis(RedisConfig等工具类编写)_第6张图片

我给大家解释一下这个@AliasFor注解,它的意思是别名的意思,看着上面的图片,也就是keyvalue互为别名,也就是说@AliasFor必须成对出现,且@AliasFor标注的字段必须设置默认值,要不然会报错

@Cacheable可以标记在一个方法上,也可以标记在一个类上。当标记在一个方法上时表示该方法是支持缓存的,当标记在一个类上时则表示该类所有的方法都是支持缓存的

//    @Cacheable(value = "'userInfo' + #p0")
//    @Cacheable(value = "userInfo",key = "#p0")
      //参数为user的话key可以使用这个属性获取到user内的属性
      //尽量使用数据的唯一变量,不能保证user.name是唯一的
//    @Cacheable(value = "userInfo",key = "#user.name")
//    @Cacheable(value = "userInfo",key = "#p0.name")
    public User getUserInfo(String name) {
        return userRepository.findByName(name);
    }

我们来看看redis中生成的key
Springboot整合Redis(RedisConfig等工具类编写)_第7张图片
就是我们通过了传进来的参数作为key,保证了每次传进来的参数不同,查询缓存返回的结果不同,这都是Redis内部封装好的key生成策略,我们也可以自定义key的生成策略

我们来重点看看这个condition

@Cacheable(value = "userInfo",key = "#p0",condition = "#p0.equals('张三')")
    public User getUserInfo(String name) {
        return userRepository.findByName(name);
    }

这个字段就是为我们做了一些过滤的操作,也就是说,加上这个字段,就可以写一些表达式,向上面这段代码,当我们传进来的参数为张三时才会进行缓存,传进其它的参数不进行缓存,这里可以使用SpringEL表达式来尽行操作

还有一些其他的属性,具体我不展开,感兴趣的在下面评论我们交流交流

@CacheEvict

Springboot整合Redis(RedisConfig等工具类编写)_第8张图片
这个注解其实就是类似清除缓存的一个触发器,当它标注在类上则说明该类的方法只要执行,都会触发清除缓存的操作,可以指定清除缓存的key(value)以及使用condition来匹配清除什么样的key

这里说一下allEntriesbeforeInvocation

allEntries表示是否需要清除缓存中的所有元素,属性为boolean,若为true则无视key(value)所设置的值,表示清除所有的元素,默认为false

beforeInvocation表示是在方法执行之前清除缓存还是执行之后清除缓存,若为true则表示方法执行前清楚缓存,默认为false,即方法执行成功后才清除缓存,若方法执行中抛出异常没执行完则不会清除缓存

@CachePut

Springboot整合Redis(RedisConfig等工具类编写)_第9张图片
与**@Cacheable**的区别是

@CachePut不会检查缓存中的数据,而是每次都会把方法执行完,把返回的结果存到指定缓存中

@Cacheable会去缓存查,如果缓存有,直接从缓存中取出来返回

@Caching

Springboot整合Redis(RedisConfig等工具类编写)_第10张图片
这个注解是可以同时设置多组**@Cacheable、@CachePut和@CacheEvict**


5.编写RedisConfig

首先我们要明白RedisConfig中需要包含什么,首先看看我们直接使用RedisTemplate的问题,我们就知道RedisConfig要包含什么了,我们在RedisConfig需要规定好根据不同的查询生成的keykeyvalue的序列化和反序列化

我们在RedisConfig中配置的自定义方法,最终通过注解引用的就是我们自定义的方法

定义key的序列化与反序列化

因为key在redis通常都是String类型的,我们定义一个将key序列化成String的一个类

实现其RedisSerializer接口,主要重写的方法是序列化的过程,因为key不一定是String类型的,还有可能是json类型,如果key为json我们先将key,转化为String类型,再序列化,防止序列化出现问题

public class StringRedisSerializer implements RedisSerializer<Object> {

    private final Charset charset;

    private final String target = "\"";

    private final String replacement = "";

    public StringRedisSerializer() {
        this(Charset.forName("UTF8"));
    }

    public StringRedisSerializer(Charset charset) {
        Assert.notNull(charset, "Charset must not be null!");
        this.charset = charset;
    }

    @Override
    public String deserialize(byte[] bytes) {
        return (bytes == null ? null : new String(bytes, charset));
    }

	//
    @Override
    public byte[] serialize(Object object) {
        String string = JSON.toJSONString(object);
        if (string == null) {
            return null;
        }
        string = string.replace(target, replacement);
        return string.getBytes(charset);
    }
}

定义value的序列化与反序列化

与原Redis默认的value序列化方法不同的是,我们选择的是FastJson来value进行序列化,而默认采用Jackson2Json来尽行序列化

默认
Springboot整合Redis(RedisConfig等工具类编写)_第11张图片
重写方法:

public class FastJsonRedisSerializer<T> implements RedisSerializer<T> {

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    @Override
    public byte[] serialize(T t) throws SerializationException {
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return (T) JSON.parseObject(str, clazz);
    }

}

PS:关于FastJson有很多争议,具体感兴趣的朋友可以去查查看,生产环境下不推荐使用fastjson,自定义JacksonJson也可以


RedisConfig的编写

最后的RedisConfig.class,继承了CachingConfigurerSupport类,重写了KeyGenerator方法,也就是key的生成策略

@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
public class RedisConfig extends CachingConfigurerSupport {

    /**
     *  设置 redis 数据默认过期时间,默认1天
     *  设置@cacheable 序列化方式
     * @return
     */
    @Bean
    public RedisCacheConfiguration redisCacheConfiguration(){
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        //设置key的时效性,一般为一天就过期
        configuration = configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofDays(1));
        //最后返回我们的配置类
        return configuration;
    }
	
	//忽略所有类型的警告
	@SuppressWarnings("all")
	//spring管理的一个bean,使用@Autowired注入到使用的类即可
    @Bean(name = "redisTemplate")
    //注册相同类型的bean,就不会成功,保持当前只有一个名为"redisTemplate"的bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        //序列化
        FastJsonRedisSerializer fastJsonRedisSerializer = new FastJsonRedisSerializer(Object.class);
        // value值的序列化采用fastJsonRedisSerializer,也就是上面我们编写的fastJsonRedisSerializer
        template.setValueSerializer(fastJsonRedisSerializer);
        template.setHashValueSerializer(fastJsonRedisSerializer);
        // 全局开启AutoType,不建议使用
        //ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        // 建议使用这种方式,小范围指定白名单
        ParserConfig.getGlobalInstance().addAccept("com.hecl.zhenghe.domain");
        // key的序列化采用StringRedisSerializer,也就是我们刚刚编写的StringRedisSerializer
        template.setKeySerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setConnectionFactory(redisConnectionFactory);
        return template;
    }

    /**
     * 自定义缓存key生成策略
     * 使用方法 @Cacheable(keyGenerator="keyGenerator")
     * @return
     */
    @Bean
    @Override	
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            StringBuilder stringBuilder = new StringBuilder();
            //加入类名
            stringBuilder.append(target.getClass().getName());
            //加入方法名
            stringBuilder.append(method.getName());
            //加入包名
            stringBuilder.append(target.getClass().getPackage());
            //加入查询参数
            for (Object obj : params) {
                stringBuilder.append(JSON.toJSONString(obj).hashCode());
            }
            //最终我们生成的key就是这四种的结合,这就能保证我们生成key的唯一性
            return stringBuilder.toString();
        };
    }
}

key的过期时间通过Duration设置还可以设置多少小时、分钟过期,下图为该类的部分方法
Springboot整合Redis(RedisConfig等工具类编写)_第12张图片
当我们重写完上边的方法之后,redis就会从默认使用的序列化反序列化、key的生成策略等都会使用我们自定义的方式了,当然还可以重写当缓存报错时的处理方式,重写下列红框的方法即可

Springboot整合Redis(RedisConfig等工具类编写)_第13张图片
其实编写RedisConfig方法就是对一些现有的Redis方法进行重写 ,让Redis使用我们重写的方法,使其更贴合我们的项目,特别是一些高并发的项目更要注重这些细节

Springboot整合Redis(RedisConfig等工具类编写)_第14张图片

转载请标注原作者,谢谢你们的支持,能给个小心心吗?

完成:2021/4/08 22:38 ALiangXLogic

后记

若有错误,欢迎指正,互相学习,相互交流

你可能感兴趣的:(Redis,Spring,boot整合)