暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

SpringBoot学习(七):集成Redis并结合Spring Cache使用

猿码记 2020-10-01
380


1. 介绍

Spring Boot 提供了对 Redis 集成的组件包:spring-boot-starter-data-redis
spring-boot-starter-data-redis
依赖于spring-data-redis
lettuce
。Spring Boot 1.0 默认使用的是 Jedis 客户端,2.0 替换成 Lettuce,但如果你从 Spring Boot 1.5.X 切换过来,几乎感受不大差异,这是因为 spring-boot-starter-data-redis
为我们隔离了其中的差异性。

Lettuce 是一个可伸缩线程安全的 Redis 客户端,多个线程可以共享同一个 RedisConnection,它利用优秀 netty NIO 框架来高效地管理多个连接。

2. 添加依赖

修改pom.xml

<!--redis 开始部分 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- lettuce pool 缓存连接池 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!--redis 结束部分 -->

3. 添加配置

修改application.yaml

server:
  port: 8080
spring:
  datasource:
    ...
  # Redis配置
  redis:
    # Redis数据库索引(默认为0
    database: 0
    # Redis服务器地址
    host: 127.0.0.1
    # 端口
    port: 6379
    # 链接超时时间 单位 ms(毫秒)
    timeout: 3000
    # Redis 线程池设置
    lettuce:
      pool:
        # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 8
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: -1
        # 连接池中的最大空闲连接 默认 8
        max-idle: 8
        # 连接池中的最小空闲连接 默认 0
        min-idle: 0

4. 自定义 RedisTemplate

默认情况下的模板只能支持 RedisTemplate<String,String>
,只能存入字符串,很多时候,我们需要自定义 RedisTemplate ,设置序列化器,这样我们可以很方便的操作实例对象。

创建: LettuceRedisConfig.java

package com.hui.javalearn.config;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.Serializable;

/**
 * 基于Lettuce操作redis的客户端
 * @author liuqh
 */

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
public class LettuceRedisConfig {

    /**
     * 自定义序列化类
     * @param connectionFactory
     * @return
     */

    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

5. 使用

package com.hui.javalearn;

import com.hui.javalearn.model.UserModel;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;

import java.io.Serializable;

@SpringBootTest
public class TestRedis {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate<String, Serializable> serializableRedisTemplate;

    /**
     * 测试字符串操作
     */

    @Test
    void testString(){
        String key = "java-str";
        String value = "hello-word";
        stringRedisTemplate.opsForValue().set(key,value);
        String s = stringRedisTemplate.opsForValue().get(key);
        System.out.println(s);
    }

    /**
     * 测试对象序列化
     */

    @Test
    void testSerializable(){
        String key = "java-obj";
        ValueOperations<String, Serializable> stringSerializableValueOperations = serializableRedisTemplate.opsForValue();
        // 具体实体类
        UserModel userModel = new UserModel();
        userModel.setPhone("17600000000");
        userModel.setNickName("张三");
        stringSerializableValueOperations.set(key,userModel);
        UserModel cacheUserModel = (UserModel)stringSerializableValueOperations.get(key);
        if (cacheUserModel != null){
            System.out.println("nickName:" + cacheUserModel.getNickName() + " phone:" + cacheUserModel.getPhone());
        }
    }
}

6. 使用Spring Cache 集成Redis

Spring Cache
具备相当的好的灵活性,不仅能够使用 SpEL(Spring Expression Language)
来定义缓存的key
和各种condition
,还提供开箱即用的缓存临时存储方案,也支持和主流的专业缓存例如EHCache
Redis
Guava
的集成。

我使用的Springboot版本为2.3.3,不需要再单独添加依赖spring-boot-starter-cache
,直接使用相关注解就行。

6.1 添加缓存管理器

修改: LettuceRedisConfig.java

package com.hui.javalearn.config;

import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.io.Serializable;
import java.time.Duration;

/**
 * 基于Lettuce操作redis的客户端
 * @author liuqh
 */

@Configuration
@AutoConfigureAfter(RedisAutoConfiguration.class)
@EnableCaching
public class LettuceRedisConfig {

    /**
     * 缓存管理器
     * 说明:会创建一个切面(aspect)并触发Spring缓存注解的切点(pointcut),
     * 根据类或者方法所使用的注解以及缓存的状态,这个切面会从缓存中获取数据,将数据添加到缓存之中或者从缓存中移除某个值。
     * @param redisConnectionFactory
     * @return
     */

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        RedisCacheConfiguration defaultCacheConfig = redisCacheConfiguration
                // 设置缓存管理器管理的缓存的默认过期时间(1小时)
                .entryTtl(Duration.ofHours(1))
                // 设置 key为string序列化
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 设置value为json序列化
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                // 不缓存空值
                .disableCachingNullValues();

        // 构造一个Redis缓存管理器
        return  RedisCacheManager.builder(redisConnectionFactory)
                // 缓存配置
                .cacheDefaults(defaultCacheConfig)
                .build();
    }


    /**
     * 自定义序列化模板
     * @param connectionFactory
     * @return
     */

    @Bean
    public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){
        // 创建一个模板类
        RedisTemplate<String, Serializable> redisTemplate = new RedisTemplate<>();
        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        // redis连接工厂 储存到模板类中
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

6.2 使用

步骤一:  在SerivceImpl使用相关注解

我的UserServiceImpl.java
部分代码

.... 
    /**
     * 测试缓存对象
     * @param phone
     * @return
     */

    @Override
    // 空值的时候不缓存
    @Cacheable(cacheNames = "userService-cache",key = "'phone_'+#phone",unless = "#result == null")
    public UserModel searchUserByPhone(String phone) {
        log.info("没有走缓存-->" + phone);
        return userDao.selectByPhone(phone);
    }

    /**
     * 测试缓存字符串
     * @param string
     * @return
     */

    @Override
    @Cacheable(cacheNames = "userService-cache",key = "'str'+#string")
    public String returnString(String string) {
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyy-MM-dd HH:mm:ss");
        String format = simpleDateFormat.format(new Date());
        return "string==> " + format;
    }
....

步骤二:  在单元测试中使用

编写单元测试: TestRedis.java

package com.hui.javalearn;

import com.hui.javalearn.service.UserService;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
public class TestRedis {

    @Autowired
    private UserService userService;

    /**
     * 测试SpringCache
     */

    @Test
    void testCacheMethod() {
        userService.searchUserByPhone("176000000");
        userService.searchUserByPhone("176000000");
        userService.searchUserByPhone("1760000411");
        userService.searchUserByPhone("1760000411");

        System.out.println(userService.returnString("a"));
        System.out.println(userService.returnString("a"));
        System.out.println(userService.returnString("b"));
        System.out.println(userService.returnString("b"));
    }
}

redis缓存情况

127.0.0.1:6379> keys *
1) "userService-cache::strb"
2) "userService-cache::stra"
3) "userService-cache::phone_1760000411"

6.3 Spring Cache 注解

注解作用
@Cacheable将方法的结果缓存起来,下一次方法执行参数相同时,将不执行方法,返回缓存中的结果
@CacheEvict移除指定缓存
@CachePut标记该注解的方法总会执行,根据注解的配置将结果缓存
@Caching可以指定相同类型的多个缓存注解,例如根据不同的条件
@CacheConfig类级别注解,可以设置一些共通的配置,@CacheConfig(cacheNames=“user”), 代表该类下的方法均使用这个cacheN

7.踩坑

7.1 @Cacheable不起作用

为了图测试方便,我直接在当前类里面定义一个方法:cacheMethod
,发现 @Cacheable不起作用,后来查资料发现不能这么用。

下面是我当时的错误使用代码:

package com.hui.javalearn;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.EnableCaching;

import java.text.SimpleDateFormat;
import java.util.Date;

@SpringBootTest
public class TestRedis {

    @Test
    void testCacheMethod(){
        System.out.println(cacheMethod("a"));
        System.out.println(cacheMethod("b"));
    }

    @Cacheable(cacheNames = "cache-name", key = "'KEY_'+#str")
    public String cacheMethod(String str){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String format = simpleDateFormat.format(new Date());
        return "cache: " +str + " Time: " + format;
    }
}

原因:   因为@Cacheable 是使用AOP 代理实现的 ,通过创建内部类来代理缓存方法,这样就会导致一个问题:类内部的方法调用类内部的缓存方法不会走代理,而不走代理,就不能正常创建缓存。

7.2 set后,出现很多\x00

代码如下

package com.hui.javalearn;


import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import java.util.concurrent.TimeUnit;

@SpringBootTest
public class TestRedis {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    /**
     * 测试字符串操作
     */

    @Test
    void testString() {
        String key = "java-str-";
        String value = "hello-word";
        stringRedisTemplate.opsForValue().set(key, value);
        // 这个结果会出现很多\x00
        stringRedisTemplate.opsForValue().set(key+"error", value,3600);
        stringRedisTemplate.opsForValue().set(key+"normal", value,3600, TimeUnit.SECONDS);
        System.out.println("o");
    }
}

存储结果


长按二维码关注最新动态

“阅读原文”我们一起进步
文章转载自猿码记,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论