009.SpringBoot集成redis

768 阅读14分钟

本节目标

  • 了解SpringBoot默认支持的两种操作redis的工具包
  • 采用lettuce操作redis
  • 了解redission
  • 采用redission操作redis

本节项目:

  • springboot + lettuce/jedis
  • springboot + redisson

传统方式操作redis

1.集成lettuce操作redis

1.1 SpringBoot三板斧之添加依赖

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

由于我们把spring-boot-dependencies的pom引入了,所以这个的版本也就定下来。

可以通过Maven Helper的插件来看看这个spring-boot-starter-data-redis的依赖关系,如下图:

image-20230609103623316

从依赖中可以看到默认依赖的是lettuce的包,这也就是为什么我们经常说spring-boot-starter-data-redis默认使用的是lettuce。

1.2 SpringBoot三板斧之添加配置

  • 单机配置

    spring:
      redis:
        host: 192.168.0.1
        port: 6379
        password: t+UUVnXZ6cysf3lr/N02d>ly+mwm/1P
        database: 0
        connect-timeout: 10s
    
  • 集群配置

    spring:
      redis:
        cluster:
          nodes: 
            - 192.168.0.1:6379
            - 192.168.0.2:6379
            - 192.168.0.3:6379
        password: t+UUVnXZ6cysf3lr/N02d>ly+mwm/1P
        timeout: 10s
    

1.3 测试reids

@SpringBootTest
public class RedisTest {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
​
    @Test
    public void testRedisTemplate() {
        redisTemplate.opsForValue().set("laoma", 55);
        Object laoma = redisTemplate.opsForValue().get("laoma");
        Console.log("laoma:{}", laoma);
    }
}

通过redis的客户端工具也能看到设置值成功了:

image-20230609143803731

但是这里的值,观察的不是很明显,因为在redis操作的时候,采用的序列化是采用默认的jdk序列化对象的方式。也就是类似字节码的形式存储。

1.4 序列化

上面提到了默认采用字节码形式存储,不太便于查看。而我们经常是采用json结构把一个对象存储到redis中,首先看看RedisTemplate这个类中默认的序列化方式,源码如下:

image-20230609134853733

Redis本身提供了一下一种序列化的方式:

  • GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化
  • Jackson2JsonRedisSerializer: 序列化object对象为json字符串
  • JdkSerializationRedisSerializer: 序列化java对象
  • StringRedisSerializer: 简单的字符串序列化

如果我们存储的是String类型,默认使用的是StringRedisSerializer 这种序列化方式。如果我们存储的是对象,默认使用的是 JdkSerializationRedisSerializer,也就是Jdk的序列化方式(通过ObjectOutputStream和ObjectInputStream实现,缺点是我们无法直观看到存储的对象内容)。

正是由于不直观,所以我们一般把对象序列化方式调整为 Jackson2JsonRedisSerializer。配置类如下:

/**
 * redis配置序列化
 *
 * @author 老马
 * @date 2023-06-09 14:58
 */
@Configuration
public class RedisConfig {
​
    @Resource
    private RedisConnectionFactory factory;
​
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisSerializer<Object> serializer = redisSerializer();
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(serializer);
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(serializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
​
    @Bean
    public RedisSerializer<Object> redisSerializer() {
        // 创建JSON序列化器
        Jackson2JsonRedisSerializer<Object> serializer =
                new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 必须设置,否则无法将json转化为对象,会转化为Map类型
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,
                ObjectMapper.DefaultTyping.NON_FINAL);
        serializer.setObjectMapper(objectMapper);
        return serializer;
    }
}

再次通过测试方法设置redis的值,采用redis客户端工具观察:

image-20230609151617263

1.5 加入连接池

有的时候我们想要给我们的redis客户端配置上连接池。就像我们连接mysql的时候,也会配置连接池一样,目的就是增加对于数据连接的管理,提升访问的效率,也保证了对资源的合理利用。

  • 修改配置文件

    spring:
      redis:
        host: 192.168.0.1
        port: 6379
        password: t+UUVnXZ6cysf3lr/N02d>ly+mwm/1P
        database: 0
        connect-timeout: 10s
        lettuce:
          pool:
            #连接池中的最小空闲连接
            min-idle: 8
            #连接池中的最大空闲连接
            max-idle: 16
            #连接池最大连接数(负值表示无限制)
            max-active: 100
    
  • 加入连接池的依赖

    <!-- 连接池 -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    

    导入连接池包前后RedisTemplate对象的对比:

    image-20230609164631044

2.集成jedis操作redis

2.1 依赖修改

<!-- reids -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <artifactId>lettuce-core</artifactId>
            <groupId>io.lettuce</groupId>
        </exclusion>
    </exclusions>
</dependency><!-- jedis -->
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

这里把SpringBoot默认的lettuce排除掉,下面引入jedis。

2.2 配置修改

spring:
  redis:
    host: 192.168.0.1
    port: 6379
    password: t+UUVnXZ6cysf3lr/N02d>ly+mwm/1P
    database: 0
    connect-timeout: 10s
    # 这里仅仅把lettuce替换为jedis即可
    # lettuce:
    jedis:
      pool:
        #连接池中的最小空闲连接
        min-idle: 8
        #连接池中的最大空闲连接
        max-idle: 16
        #连接池最大连接数(负值表示无限制)
        max-active: 100

2.3 进行测试

image-20230609171243012

3.lettuce和jedis丝滑切换的原理

SpringBoot框架能通过各种starter进行无缝集成一个主要原因是其自动化配置功能。所谓自动化配置就是springBoot本身已经预先设置好了一些常用框架的整合类。然后通过类似于ConditionOn这样的条件判断注解,去辨别你的项目中是否有相关的类(或配置)了,进而进行相关配置的初始化。springBoot预设的自动化配置类都位于spring-boot-autoconfigure这个包中,只要我们搭建了springBoot的项目,这个包就会被引入进来。

image-20230609173302618

这里可以看到这个RedisAutoConfiguration的配置类,打开看下:

image-20230609173717408

再次打开引入的配置类看下:

  • lettuce

    image-20230609174134511

  • Jedis

    image-20230609174251724

至此就明白为什么我们把lettuce去掉引入jedis的依赖后,马上就采用jedis进行redis操作了。

4.redis工具类

package com.mayuanfei.springboot08.util;
​
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
​
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.TimeUnit;
​
/**
 * Redis工具类
 * @author 老马
 * @date 2023-06-09 17:17
 */
@Component
public class RedisUtil {
​
    @Autowired
    private RedisTemplate redisTemplate;
​
    /**
     * 给一个指定的 key 值附加过期时间
     *
     * @param key
     * @param time
     * @return
     */
    public boolean expire(String key, long time) {
        return redisTemplate.expire(key, time, TimeUnit.SECONDS);
    }
​
    /**
     * 根据key 获取过期时间
     *
     * @param key
     * @return
     */
    public long getTime(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }
​
    /**
     * 根据key 获取过期时间
     *
     * @param key
     * @return
     */
    public boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }
​
    /**
     * 移除指定key 的过期时间
     *
     * @param key
     * @return
     */
    public boolean persist(String key) {
        return redisTemplate.boundValueOps(key).persist();
    }
​
    //- - - - - - - - - - - - - - - - - - - - -  String类型 - - - - - - - - - - - - - - - - - 
​
    /**
     * 根据key获取值
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }
​
    /**
     * 将值放入缓存
     *
     * @param key   键
     * @param value 值
     * @return true成功 false 失败
     */
    public void set(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }
​
    /**
     * 将值放入缓存并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) -1为无期限
     * @return true成功 false 失败
     */
    public void set(String key, String value, long time) {
        if (time > 0) {
            redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
        } else {
            redisTemplate.opsForValue().set(key, value);
        }
    }
​
    /**
     * 批量添加 key (重复的键会覆盖)
     *
     * @param keyAndValue
     */
    public void batchSet(Map<String, String> keyAndValue) {
        redisTemplate.opsForValue().multiSet(keyAndValue);
    }
​
    /**
     * 批量添加 key-value 只有在键不存在时,才添加
     * map 中只要有一个key存在,则全部不添加
     *
     * @param keyAndValue
     */
    public void batchSetIfAbsent(Map<String, String> keyAndValue) {
        redisTemplate.opsForValue().multiSetIfAbsent(keyAndValue);
    }
​
    /**
     * 对一个 key-value 的值进行加减操作,
     * 如果该 key 不存在 将创建一个key 并赋值该 number
     * 如果 key 存在,但 value 不是长整型 ,将报错
     *
     * @param key
     * @param number
     */
    public Long increment(String key, long number) {
        return redisTemplate.opsForValue().increment(key, number);
    }
​
    /**
     * 对一个 key-value 的值进行加减操作,
     * 如果该 key 不存在 将创建一个key 并赋值该 number
     * 如果 key 存在,但 value 不是 纯数字 ,将报错
     *
     * @param key
     * @param number
     */
    public Double increment(String key, double number) {
        return redisTemplate.opsForValue().increment(key, number);
    }
​
    //- - - - - - - - - - - - - - - - - - - - -  set类型 - - - - - - - - - - - - - - - - - - 
​
    /**
     * 将数据放入set缓存
     *
     * @param key 键
     * @return
     */
    public void sSet(String key, String value) {
        redisTemplate.opsForSet().add(key, value);
    }
​
    /**
     * 获取变量中的值
     *
     * @param key 键
     * @return
     */
    public Set<Object> members(String key) {
        return redisTemplate.opsForSet().members(key);
    }
​
    /**
     * 随机获取变量中指定个数的元素
     *
     * @param key   键
     * @param count 值
     * @return
     */
    public void randomMembers(String key, long count) {
        redisTemplate.opsForSet().randomMembers(key, count);
    }
​
    /**
     * 随机获取变量中的元素
     *
     * @param key 键
     * @return
     */
    public Object randomMember(String key) {
        return redisTemplate.opsForSet().randomMember(key);
    }
​
    /**
     * 弹出变量中的元素
     *
     * @param key 键
     * @return
     */
    public Object pop(String key) {
        return redisTemplate.opsForSet().pop("setValue");
    }
​
    /**
     * 获取变量中值的长度
     *
     * @param key 键
     * @return
     */
    public long size(String key) {
        return redisTemplate.opsForSet().size(key);
    }
​
    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }
​
    /**
     * 检查给定的元素是否在变量中。
     *
     * @param key 键
     * @param obj 元素对象
     * @return
     */
    public boolean isMember(String key, Object obj) {
        return redisTemplate.opsForSet().isMember(key, obj);
    }
​
    /**
     * 转移变量的元素值到目的变量。
     *
     * @param key     键
     * @param value   元素对象
     * @param destKey 元素对象
     * @return
     */
    public boolean move(String key, String value, String destKey) {
        return redisTemplate.opsForSet().move(key, value, destKey);
    }
​
    /**
     * 批量移除set缓存中元素
     *
     * @param key    键
     * @param values 值
     * @return
     */
    public void remove(String key, Object... values) {
        redisTemplate.opsForSet().remove(key, values);
    }
​
    /**
     * 通过给定的key求2个set变量的差值
     *
     * @param key     键
     * @param destKey 键
     * @return
     */
    public Set<Set> difference(String key, String destKey) {
        return redisTemplate.opsForSet().difference(key, destKey);
    }
​
​
    //- - - - - - - - - - - - - - - - - - - - -  hash类型 - - - - - - - - - - - - - - - - - - 
​
    /**
     * 加入缓存
     *
     * @param key 键
     * @param map 键
     * @return
     */
    public void add(String key, Map<String, String> map) {
        redisTemplate.opsForHash().putAll(key, map);
    }
​
    /**
     * 获取 key 下的 所有  hashkey 和 value
     *
     * @param key 键
     * @return
     */
    public Map<Object, Object> getHashEntries(String key) {
        return redisTemplate.opsForHash().entries(key);
    }
​
    /**
     * 验证指定 key 下 有没有指定的 hashkey
     *
     * @param key
     * @param hashKey
     * @return
     */
    public boolean hashKey(String key, String hashKey) {
        return redisTemplate.opsForHash().hasKey(key, hashKey);
    }
​
    /**
     * 获取指定key的值string
     *
     * @param key  键
     * @param key2 键
     * @return
     */
    public String getMapString(String key, String key2) {
        return redisTemplate.opsForHash().get("map1", "key1").toString();
    }
​
    /**
     * 获取指定的值Int
     *
     * @param key  键
     * @param key2 键
     * @return
     */
    public Integer getMapInt(String key, String key2) {
        return (Integer) redisTemplate.opsForHash().get("map1", "key1");
    }
​
    /**
     * 弹出元素并删除
     *
     * @param key 键
     * @return
     */
    public String popValue(String key) {
        return redisTemplate.opsForSet().pop(key).toString();
    }
​
    /**
     * 删除指定 hash 的 HashKey
     *
     * @param key
     * @param hashKeys
     * @return 删除成功的 数量
     */
    public Long delete(String key, String... hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }
​
    /**
     * 给指定 hash 的 hashkey 做增减操作
     *
     * @param key
     * @param hashKey
     * @param number
     * @return
     */
    public Long increment(String key, String hashKey, long number) {
        return redisTemplate.opsForHash().increment(key, hashKey, number);
    }
​
    /**
     * 给指定 hash 的 hashkey 做增减操作
     *
     * @param key
     * @param hashKey
     * @param number
     * @return
     */
    public Double increment(String key, String hashKey, Double number) {
        return redisTemplate.opsForHash().increment(key, hashKey, number);
    }
​
    /**
     * 获取 key 下的 所有 hashkey 字段
     *
     * @param key
     * @return
     */
    public Set<Object> hashKeys(String key) {
        return redisTemplate.opsForHash().keys(key);
    }
​
    /**
     * 获取指定 hash 下面的 键值对 数量
     *
     * @param key
     * @return
     */
    public Long hashSize(String key) {
        return redisTemplate.opsForHash().size(key);
    }
​
    //- - - - - - - - - - - - - - - - - - - - -  list类型 - - - - - - - - - - - - - - - - - - 
​
    /**
     * 在变量左边添加元素值
     *
     * @param key
     * @param value
     * @return
     */
    public void leftPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }
​
    /**
     * 获取集合指定位置的值。
     *
     * @param key
     * @param index
     * @return
     */
    public Object index(String key, long index) {
        return redisTemplate.opsForList().index("list", 1);
    }
​
    /**
     * 获取指定区间的值。
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public List<Object> range(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }
​
    /**
     * 把最后一个参数值放到指定集合的第一个出现中间参数的前面,
     * 如果中间参数值存在的话。
     *
     * @param key
     * @param pivot
     * @param value
     * @return
     */
    public void leftPush(String key, String pivot, String value) {
        redisTemplate.opsForList().leftPush(key, pivot, value);
    }
​
    /**
     * 向左边批量添加参数元素。
     *
     * @param key
     * @param values
     * @return
     */
    public void leftPushAll(String key, String... values) {
        redisTemplate.opsForList().leftPushAll(key, values);
    }
​
    /**
     * 向集合最右边添加元素。
     *
     * @param key
     * @param value
     * @return
     */
    public void leftPushAll(String key, String value) {
        redisTemplate.opsForList().rightPush(key, value);
    }
​
    /**
     * 向左边批量添加参数元素。
     *
     * @param key
     * @param values
     * @return
     */
    public void rightPushAll(String key, String... values) {
        redisTemplate.opsForList().rightPushAll(key, values);
    }
​
    /**
     * 向已存在的集合中添加元素。
     *
     * @param key
     * @param value
     * @return
     */
    public void rightPushIfPresent(String key, Object value) {
        redisTemplate.opsForList().rightPushIfPresent(key, value);
    }
​
    /**
     * 向已存在的集合中添加元素。
     *
     * @param key
     * @return
     */
    public long listLength(String key) {
        return redisTemplate.opsForList().size(key);
    }
​
    /**
     * 移除集合中的左边第一个元素。
     *
     * @param key
     * @return
     */
    public void leftPop(String key) {
        redisTemplate.opsForList().leftPop(key);
    }
​
    /**
     * 移除集合中左边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
     *
     * @param key
     * @return
     */
    public void leftPop(String key, long timeout, TimeUnit unit) {
        redisTemplate.opsForList().leftPop(key, timeout, unit);
    }
​
    /**
     * 移除集合中右边的元素。
     *
     * @param key
     * @return
     */
    public void rightPop(String key) {
        redisTemplate.opsForList().rightPop(key);
    }
​
    /**
     * 移除集合中右边的元素在等待的时间里,如果超过等待的时间仍没有元素则退出。
     *
     * @param key
     * @return
     */
    public void rightPop(String key, long timeout, TimeUnit unit) {
        redisTemplate.opsForList().rightPop(key, timeout, unit);
    }
}

5.代码地址

gitee.com/mayuanfei/S…下的springboot08

redission方式操作redis

1.简介

redisson是Redis官方推荐的一个客户端工具。它提供了使用Redis的最简单和最便捷的方法。除此之外,redisson还提供了可靠的分布式锁、布隆过滤器等功能。

文档地址: redisson中文文档

2.SpringBoot三板斧之添加依赖

官方比较推荐的starter包配置方式,这种方式会深度和Spring Data Redis模块结合。所以官方要求redisson-spring-data要和springboot的版本配合使用。否则有可能会引起莫名的问题。对应关系如下:

redisson-spring-data module nameSpring Boot version
redisson-spring-data-161.3.y
redisson-spring-data-171.4.y
redisson-spring-data-181.5.y
redisson-spring-data-2x2.x.y
redisson-spring-data-3x3.x.y

这也就是说,redisson的starter可以比较新,但是redisson-spring-data要和springboot的版本对应。示例中的springboot的版本为2.7.10版本,pom引入:

<!-- redisson-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.21.3</version>
    <exclusions>
        <exclusion>
            <artifactId>redisson-spring-data-30</artifactId>
            <groupId>org.redisson</groupId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-data-27</artifactId>
    <version>3.21.3</version>
</dependency>

3.SpringBoot三板斧之添加配置

spring:
  redis:
    # 地址
    host: 192.168.0.1
    # 端口,默认为6379
    port: 6379
    # 数据库索引
    database: 0
    # 密码(如没有密码请注释掉)
    password: t+UUVnXZ6cys23dsd2d>ly+mwm/1P
    # 连接超时时间
    timeout: 10s
    # 是否开启ssl
    ssl: false
# redisson配置
redisson:
  # redis key前缀
  keyPrefix: springboot09
  # 线程池数量
  threads: 4
  # Netty线程池数量
  nettyThreads: 8
  # 单节点配置
  singleServerConfig:
    # 客户端名称
    clientName: laoma_macos
    # 最小空闲连接数
    connectionMinimumIdleSize: 8
    # 连接池大小
    connectionPoolSize: 32
    # 连接空闲超时,单位:毫秒
    idleConnectionTimeout: 10000
    # 命令等待超时,单位:毫秒
    timeout: 3000
    # 发布和订阅连接池大小
    subscriptionConnectionPoolSize: 50
    

4.redisson配置类

redisson不但能操作redis,还依照Spring Cache标准提供了基于Redis的Spring缓存实现,每个缓存(Cache)实例都提供了了两个重要的可配置参数:过期时间(ttl)和最长空闲时间(maxIdleTime),如果这两个参数都未指定或值为0,那么实例管理的数据将永久保存。相对而已还比较复杂,配置类代码如下:

@Slf4j
@Configuration
@EnableCaching
@EnableConfigurationProperties(RedissonProperties.class)
public class RedisConfig {
​
    @Resource
    private RedissonProperties redissonProperties;
​
    @Resource
    private ObjectMapper objectMapper;
​
    @Bean
    public RedissonAutoConfigurationCustomizer redissonCustomizer() {
        return config -> {
            config.setThreads(redissonProperties.getThreads())
                .setNettyThreads(redissonProperties.getNettyThreads())
                .setCodec(new JsonJacksonCodec(objectMapper));
            RedissonProperties.SingleServerConfig singleServerConfig = redissonProperties.getSingleServerConfig();
            if (ObjectUtil.isNotNull(singleServerConfig)) {
                // 使用单机模式
                config.useSingleServer()
                    .setTimeout(singleServerConfig.getTimeout())
                    .setClientName(singleServerConfig.getClientName())
                    .setIdleConnectionTimeout(singleServerConfig.getIdleConnectionTimeout())
                    .setSubscriptionConnectionPoolSize(singleServerConfig.getSubscriptionConnectionPoolSize())
                    .setConnectionMinimumIdleSize(singleServerConfig.getConnectionMinimumIdleSize())
                    .setConnectionPoolSize(singleServerConfig.getConnectionPoolSize());
            }
            // 集群配置方式
            RedissonProperties.ClusterServersConfig clusterServersConfig = redissonProperties.getClusterServersConfig();
            if (ObjectUtil.isNotNull(clusterServersConfig)) {
                config.useClusterServers()
                    .setTimeout(clusterServersConfig.getTimeout())
                    .setClientName(clusterServersConfig.getClientName())
                    .setIdleConnectionTimeout(clusterServersConfig.getIdleConnectionTimeout())
                    .setSubscriptionConnectionPoolSize(clusterServersConfig.getSubscriptionConnectionPoolSize())
                    .setMasterConnectionMinimumIdleSize(clusterServersConfig.getMasterConnectionMinimumIdleSize())
                    .setMasterConnectionPoolSize(clusterServersConfig.getMasterConnectionPoolSize())
                    .setSlaveConnectionMinimumIdleSize(clusterServersConfig.getSlaveConnectionMinimumIdleSize())
                    .setSlaveConnectionPoolSize(clusterServersConfig.getSlaveConnectionPoolSize())
                    .setReadMode(clusterServersConfig.getReadMode())
                    .setSubscriptionMode(clusterServersConfig.getSubscriptionMode());
            }
            log.info("初始化 redis 配置");
        };
    }
    
    /**
     * reission实现的缓存管理器 整合spring-cache
     */
    @Bean
    public CacheManager cacheManager() {
        return new RedissonSpringCacheManager(SpringUtil.getBean(RedissonClient.class));
    }
}

这里配置的cacheManager采用的默认的redisson类。这种方式少了过期时间、最大空闲时间和组最大长度的配置,如果想更完善的使用spring cache,可以参考:blog.csdn.net/weixin_4046…这里进行配置。

5.测试类

Spring-cache注释的使用参考:blog.csdn.net/m0_67698950…

/**
 * 表示这个方法有了缓存的功能,方法的返回值会被缓存下来
 * 下一次调用该方法前,会去检查是否缓存中已经有值
 * 如果有就直接返回,不调用方法
 * 如果没有,就调用方法,然后把结果缓存起来
 * 这个注解「一般用在查询方法上」
 */
@Cacheable(cacheNames = "test:demo", key = "#userid", condition = "#userid != null")
@GetMapping("/test3")
public SysUser testRedis3(Long userid) {
    SysUser user = new SysUser(userid, "测试用户" + userid, "昵称" + userid);
    return user;
}
​
/**
 * 会把方法的返回值put到缓存里面缓存起来,供其它地方使用
 * 它「通常用在新增或者实时更新方法上」
 */
@CachePut(cacheNames = "test:demo", key = "#userid", condition = "#userid != null")
@GetMapping("/test4")
public SysUser testRedis4(Long userid) {
    SysUser user = new SysUser(userid, "测试用户" + userid, "昵称" + userid);
    return user;
}
​
/**
 * 使用了CacheEvict注解的方法,会清空指定缓存
 * 「一般用在删除的方法上」
 */
@CacheEvict(cacheNames = "test:demo", key = "#userid", condition = "#userid != null")
@GetMapping("/test5")
public String testRedis5(Long userid) {
    return "删除成功";
}

6.代码地址

gitee.com/mayuanfei/S…下的springboot09

redisson极简模式集成及其分布式锁使用

1.极简模式集成redisson

1.1加入依赖

<!-- redisson-->
<dependency>
    <groupId>org.redisson</groupId>
    <artifactId>redisson-spring-boot-starter</artifactId>
    <version>3.21.3</version>
</dependency>

1.2添加配置

# redis基础配置
spring:
  redis:
    # 地址
    host: 192.168.0.1
    # 端口,默认为6379
    port: 6379
    # 数据库索引
    database: 0
    # 密码(如没有密码请注释掉)
    password: t+UUVnXZ6cys23dsd2d>ly+mwm/1P
    # 连接超时时间
    timeout: 10s
    # 是否开启ssl
    ssl: false

如果想对redisson有更多的配置,可以加入redssion的配置文件

spring:
  redis:
    redisson:
      file: classpath:redisson.yaml

配置文件可以参考: redisson中文文档

1.3测试通过redisson操作redis

@Resource
private RedissonClient redissonClient;
​
// 通过redisson操作redis
@GetMapping("/test1")
public String testRedis1() {
    JsonJacksonCodec jacksonCodec = new JsonJacksonCodec();
    Map<String, Integer> map  = new HashMap<>();
    map.put("laoma", 44);
    map.put("cuidk", 43);
    RMap<Object, Object> lala = this.redissonClient.getMap("lala", jacksonCodec);
    lala.putAll(map);
    RBucket<Object> bucket = this.redissonClient.getBucket("aa:bb:cc", jacksonCodec);
    bucket.set(124);
    return JSONUtil.toJsonStr(lala);
}

2.redisson实现分布式锁

分布式锁的应用场景:我们在用nginx把同一个程序进行集群部署时,由于每一个springboot程序都是单独的JVM,所以传统的synchronized和ReentrantLock已经都不好使了。这时,就考虑用redisson的分布式锁。

2.1锁原理

image-20230614110451194

2.2示例代码

@Resource
private RedissonClient redissonClient;
​
public void updateUser(String userId) {
  String lockKey = "user" + userId;
  RLock lock = redissonClient.getLock(lockKey);  //获取锁资源
  try {
    lock.lock(10, TimeUnit.SECONDS);   //加锁,可以指定锁定时间
​
    //这里写需要处理业务的业务代码
  } finally {
    lock.unlock();   //释放锁
  }
}
​

2.3代码地址

gitee.com/mayuanfei/S…下的springboot10

记忆印记

  • springboot默认继承lettuce来操作redis的
  • 可以利用redisson来操作redis,而且redisson封装了很多好用的方法。以后操作redis优先使用redisson
  • redisson可以把java现有的类型,缓存到redis中,也就是可以在集群系统中,像访问同一个数据库一样
  • 在集群或者分布式系统中,我们经常用redisson来实现分布式锁。