SpringBoot之集成Redis

SpringBoot之集成Redis

  • 一、Redis集成简介
  • 二、集成步骤
      • 2.1 添加依赖
      • 2.2 添加配置
      • 2.3 项目中使用
  • 三、工具类封装
  • 四、序列化 (正常都需要自定义序列化)
  • 五、分布式锁

一、Redis集成简介

Redis是我们Java开发中,使用频次非常高的一个nosql数据库,数据以key-value键值对的形式存储在内存中。redis的常用使用场景,可以做缓存,分布式锁,自增序列等,使用redis的方式和我们使用数据库的方式差不多,首先我们要在自己的本机电脑或者服务器上安装一个redis的服务器,通过我们的java客户端在程序中进行集成,然后通过客户端完成对redis的增删改查操作。

redis的Java客户端类型还是很多的,常见的有jedis, redission,lettuce等,所以我们在集成的时候,我们可以选择直接集成这些原生客户端。
但是在springBoot中更常见的方式是集成spring-data-redis,这是spring提供的一个专门用来操作redis的项目,封装了对redis的常用操作,里边主要封装了jedis和lettuce两个客户端。相当于是在他们的基础上加了一层门面。

二、集成步骤

2.1 添加依赖

添加redis所需依赖:(spring-boot-starter-data-redis 默认使用的就是lettuce这个客户端)

<!-- 集成redis依赖  -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

完整pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lsqingfeng.springboot</groupId>
    <artifactId>springboot-learning</artifactId>
    <version>1.0.0</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>2.6.2</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.22</version>
            <scope>provided</scope>
        </dependency>

        <!-- mybatis-plus 所需依赖  -->
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.5.1</version>
        </dependency>

        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>

        <!-- 开发热启动 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <!-- MySQL连接 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>

        <!-- 集成redis依赖  -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
    </dependencies>
</project>

注意点:如果我们想要使用jedis客户端怎么办呢?就需要排除lettuce这个依赖,再引入jedis的相关依赖就可以了。

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

<dependency>  
    <groupId>redis.clients</groupId>  
    <artifactId>jedis</artifactId>  
    <version>你的Jedis版本号</version>  
</dependency>

两者的区别

Lettuce更适合需要异步处理、线程安全以及支持哨兵和集群模式的场景(线程安全);
而Jedis则更适合简单的同步操作,以及在不需要哨兵和集群模式的场景中使用(线程不安全)。

2.2 添加配置

然后我们需要配置连接redis所需的账号密码等信息,这里大家要提前安装好redis,保证我们的本机程序可以连接到我们的redis, 如果不知道redis如何安装,可以参考文章: [Linux系统安装redis6.0.5] blog.csdn.net/lsqingfeng/…

常规配置如下: 在application.yml配置文件中配置 redis的连接信息

spring:
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 0

如果有其他配置放到一起:

server:
  port: 19191

spring:
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/springboot_learning?serverTimezone=Asia/Shanghai&characterEncoding=utf-8
    username: root
    password: root
  redis:
    host: localhost
    port: 6379
    password: 123456
    database: 0
    lettuce:
      pool:
        max-idle: 16
        max-active: 32
        min-idle: 8
  devtools:
    restart:
      enable: true


third:
  weather:
    url: http://www.baidu.com
    port: 8080
    username: test
    cities:
      - 北京
      - 上海
      - 广州
    list[0]: aaa
    list[1]: bbb
    list[2]: ccc

这样我们就可以直接在项目当中操作redis了。如果使用的是集群,那么使用如下配置方式:

spring:
  redis:
    password: 123456
    cluster:
      nodes: 10.255.144.115:7001,10.255.144.115:7002,10.255.144.115:7003,10.255.144.115:7004,10.255.144.115:7005,10.255.144.115:7006
      max-redirects: 3

但是有的时候我们想要给我们的redis客户端配置上连接池。
就像我们连接mysql的时候,也会配置连接池一样,目的就是增加对于数据连接的管理,提升访问的效率,也保证了对资源的合理利用。那么我们如何配置连接池呢,这里大家一定要注意了,很多网上的文章中,介绍的方法可能由于版本太低,都不是特别的准确。
比如很多人使用spring.redis.pool来配置,这个是不对的(不清楚是不是老版本是这样的配置的,但是在springboot-starter-data-redis中这种写法不对)。首先是配置文件,由于我们使用的lettuce客户端,所以配置的时候,在spring.redis下加上lettuce再加上pool来配置,具体如下;

spring:
  redis:
    host: 10.255.144.111
    port: 6379
    password: 123456
    database: 0
    lettuce:
      pool:
        max-idle: 16
        max-active: 32
        min-idle: 8

如果使用的是jedis,就把lettuce换成jedis(同时要注意依赖也是要换的)。

但是仅仅这在配置文件中加入,其实连接池是不会生效的。这里大家一定要注意,很多同学在配置文件上加上了这段就以为连接池已经配置好了,其实并没有,还少了最关键的一步,就是要导入一个依赖,不导入的话,这么配置也没有用。

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

之后,连接池才会生效。我们可以做一个对比。 在导包前后,观察RedisTemplate对象的值就可以看出来。

导入之前:

导入之后:

导入之后,我们的连接池信息才有值,这也印证了我们上面的结论。

具体的配置信息我们可以看一下源代码,源码中使用RedisProperties 这个类来接收redis的配置参数。

2.3 项目中使用

我们的配置工作准备就绪以后,我们就可以在项目中操作redis了,操作的话,使用spring-data-redis中为我们提供的 RedisTemplate 这个类,就可以操作了。我们先举个简单的例子,插入一个键值对(值为string)。

package com.lsqingfeng.springboot.controller;

import com.lsqingfeng.springboot.base.Result;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * @className: RedisController
 * @description:
 * @author: sh.Liu
 * @date: 2022-03-08 14:28
 */
@RestController
@RequestMapping("redis")
public class RedisController {

    private final RedisTemplate redisTemplate;

    public RedisController(RedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @GetMapping("save")
    public Result save(String key, String value){
        redisTemplate.opsForValue().set(key, value);
        return Result.success();
    }

}

三、工具类封装

package com.lsqingfeng.springboot.utils;
 
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;
 
/**
 * @className: RedisUtil
 * @description:
 * @author: sh.Liu
 * @date: 2022-03-09 14:07
 */
@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,"w","x","y");
        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().leftPushAll(key,"w","x","y");
        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);
    }
}

四、序列化 (正常都需要自定义序列化)

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

  • GenericToStringSerializer: 可以将任何对象泛化为字符串并序列化
  • Jackson2JsonRedisSerializer: 跟JacksonJsonRedisSerializer实际上是一样的
  • JacksonJsonRedisSerializer: 序列化object对象为json字符串
  • JdkSerializationRedisSerializer: 序列化java对象
  • StringRedisSerializer: 简单的字符串序列化

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

通过观察RedisTemplate的源码我们就可以看出来,默认使用的是JdkSerializationRedisSerializer. 这种序列化最大的问题就是存入对象后,我们很难直观看到存储的内容,很不方便我们排查问题:

而一般我们最经常使用的对象序列化方式是: Jackson2JsonRedisSerializer

设置序列化方式的主要方法就是我们在配置类中,自己来创建RedisTemplate对象,并在创建的过程中指定对应的序列化方式。

@Configuration  
public class RedisConfig {  
    // 定义一个Bean,名称为"redisTemplate",返回类型为RedisTemplate<String, Object>  
    @Bean(name = "redisTemplate")  
    public RedisTemplate<String, Object> getRedisTemplate(RedisConnectionFactory factory) {  
        // 创建一个新的RedisTemplate实例,用于操作Redis  
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<String, Object>();  
        // 设置RedisTemplate使用的连接工厂,以便它能够连接到Redis服务器  
        redisTemplate.setConnectionFactory(factory);  
          
        // 创建一个StringRedisSerializer实例,用于序列化Redis的key为字符串  
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();  
          
        // 创建一个Jackson2JsonRedisSerializer实例,用于序列化Redis的value为JSON格式  
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);  
          
        // 创建一个ObjectMapper实例,用于处理JSON的序列化和反序列化  
        ObjectMapper objectMapper = new ObjectMapper();  
          
        // 设置ObjectMapper的属性访问级别,以便能够序列化对象的所有属性  
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);  
          
        // 启用默认的类型信息,以便在反序列化时能够知道对象的实际类型  
        // 注意:这里使用了新的方法替换了过期的enableDefaultTyping方法  
        // 方法过期,改为下面代码  
        // objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);  
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,  
                ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);  
          
        // 设置Jackson2JsonRedisSerializer使用的ObjectMapper  
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);  
  
        // 设置RedisTemplate的key序列化器为stringRedisSerializer  
        redisTemplate.setKeySerializer(stringRedisSerializer); // key的序列化类型  
        // 设置RedisTemplate的value序列化器为jackson2JsonRedisSerializer  
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer); // value的序列化类型  
          
        // 设置RedisTemplate的hash key序列化器为stringRedisSerializer  
        redisTemplate.setHashKeySerializer(stringRedisSerializer);  // key的序列化类型  
        // 设置RedisTemplate的hash value序列化器为jackson2JsonRedisSerializer  
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);   // value的序列化类型  
        
        // 调用RedisTemplate的afterPropertiesSet方法,该方法会执行一些初始化操作,比如检查序列化器是否设置等  
        redisTemplate.afterPropertiesSet();  
          
        // 返回配置好的RedisTemplate实例  
        return redisTemplate;  
    }  
}

这样使用的时候,就会按照我们设置的json序列化方式进行存储,我们也可以在redis中查看内容的时候方便的查看到属性值。

五、分布式锁

在分布式系统中,你可能想要确保某个操作只执行一次。通过设置一个唯一的键,并设置适当的过期时间,你可以实现一个简单的分布式锁。

//Duration.ofMinutes(30):键将在30分钟后自动从Redis中删除
//boolean flag: 如果键被成功设置(即它之前不存在),则此方法返回 true。如果键已经存在,因此没有被设置,则返回 false。
//可以使用该方式实现一段逻辑在分布式系统中30分钟之内只执行一次
boolean flag = redisUtil.setIfAbsent("flag", l, Duration.ofMinutes(30));

参考文章
【1】SpringBoot教程(十四) | SpringBoot集成Redis(全网最全)
https://juejin.cn/post/7076244567569203208#heading-7

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/529767.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【双指针】接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图&#xff0c;计算按此排列的柱子&#xff0c;下雨之后能接多少雨 示例 1&#xff1a; 输入&#xff1a;height [0,1,0,2,1,0,1,3,2,1,2,1]输出&#xff1a;6解释&#xff1a;上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的…

逆向案例十七(1)——webpack加如果之前发送公钥如何定位参数,基于中国五矿

网址链接&#xff1a;中国五矿集团有限公司采购电子商务平台 定位到数据包&#xff0c;载荷中param是一个加密参数。 每一个数据包前都有一个public返回公钥。 点击查看返回的数据 如何定位参数加密位置&#xff1f; 复制公钥包url的后面&#xff0c;进行搜索 &#xff0c;查…

无需训练,这个新方法实现了生成图像尺寸、分辨率自由

ChatGPT狂飙160天&#xff0c;世界已经不是之前的样子。 新建了免费的人工智能中文站https://ai.weoknow.com 新建了收费的人工智能中文站https://ai.hzytsoft.cn/ 更多资源欢迎关注 近日&#xff0c;来自香港中文大学 - 商汤科技联合实验室等机构的研究者们提出了FouriScale&…

C语言单链表

1. 单链表的概念和结构 概念&#xff1a;链表是一种物理存储结构上非连续、非顺序的存储结构&#xff0c;数据元素的逻辑顺序是通过链表 中的指针链接次序实现的 。 链表与顺序表都属于线性表&#xff0c;顺序表在物理存储结构上是线性的&#xff0c;但是链表在物理存储结构上…

react-静态组件,动态组件

react09- 组件 静态组件 动态组件 静态组件&#xff1a; 函数组件&#xff0c;在第一次渲染完成后&#xff0c;组件中的内容&#xff0c;不会根据组件内的某些操作再次进行更新&#xff0c;页面并不会跟着改变 过程&#xff1a; 第一次渲染时&#xff0c;执行函数方法&#x…

202458读书笔记|《风来自你的方向》——我每次见你时的百米冲刺,加起来就是一生的长跑

《风来自你的方向》隔花人著 大绵羊BOBO绘&#xff0c;狗狗&#x1f436;绘本&#xff0c;这是看的第3本书。上俩本是《我是你的小狗 狗狗心事绘本》&#xff0c;《我是你的小狗2 当我有了你》。 同样的简短文字小狗&#x1f436;漫画&#xff0c;有爱的主人&#xff0c;有趣…

[中级]软考_软件设计_计算机组成与体系结构_12_概述及回顾

概述及回顾 总纲考情分析与分值海明校验码计算公式重点 总纲 考情分析与分值 海明校验码计算公式 2 r m r 1 2^r mr1 2rmr1 重点 数据的表示是计算题型的基础计算机组成中的CPU组成计算机组成中的存储系统&#xff0c;是核心重点的考察CISC与RISC及流水线执行时间的求取

lgwr超时如何判断存储还是cpu问题?(等待事件各种类型和说明及相关查询)

通过awr报告看&#xff1a; 分析&#xff1a; log file parallel write平均等待8毫秒 log file sync平均等待402毫秒 排查&#xff1a; log file sync parallel write lgwr cpu log file parallel write等待少说明存储不慢。 所以&#xff1a;log file sync等待长是因为…

css字体相关属性

属性汇总 属性作用font-family 设置文章字体 font-size 设置字体大小 font-weight设置字体粗细font-style设置字体斜体font总体设置以上属性 设置文章字体 font-family属性 案例&#xff1a; 设置字体大小 font-size属性 注意事项&#xff1a; 1.必须要加单位&#xff0…

很牛的一套仓库管理系统,免费复用【带源码】

今天给大家分享一套基于SpringbootVue的仓库管理系统源码&#xff0c;在实际项目中可以直接复用。(免费提供&#xff0c;文末自取) ​一、系统运行图&#xff08;设计报告和接口文档&#xff09; 1、登陆页面 2、物品信息管理 3、设计报告包含接口文档 二、系统搭建视频教程 …

Training - Kubeflow 的 PyTorchJob 配置 DDP 分布式训练 (ncclInternalError)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://blog.csdn.net/caroline_wendy/article/details/137569332 Kubeflow 的 PyTorchJob 是 Kubernetes 自定义资源&#xff0c;用于在 Kubernetes 上运行 PyTorch 训练任务&#xff0c;是 K…

谷歌浏览器快捷键, VScode 快捷键

谷歌浏览器快捷键 谷歌浏览器跳转标签页的方式&#xff1a; control Tab 跳转下一个标签页 control shift tab 上一个标签页 command 1-8 跳转对应的标签页&#xff0c;而command 9 则是跳转最后一个标签页 Previous Tab 插件实现谷歌浏览器两个tab页来回切换。快捷键为…

猫头虎分享已解决Error: 已解决“ModuleNotFoundError: No module named“

博主猫头虎的技术世界 &#x1f31f; 欢迎来到猫头虎的博客 — 探索技术的无限可能&#xff01; 领域矩阵&#xff1a; &#x1f310; 猫头虎技术领域矩阵&#xff1a; 深入探索各技术领域&#xff0c;发现知识的交汇点。了解更多&#xff0c;请访问&#xff1a; 猫头虎技术矩阵…

Docker入门指南:从安装到基本操作和镜像构建的全面教程

文章目录 一、Docker简介二、Docker的安装三、Docker的基本概念四、Docker的基本操作五、Dockerfile和镜像构建六、总结 一、Docker简介 Docker是一个开源的应用容器引擎&#xff0c;它允许开发者将应用程序及其依赖项打包到一个可移植的容器中&#xff0c;然后在任何支持Dock…

24/04/09总结

异常: 1.异常是什么? 程序中可能出现的问题 2.异常体系的最上层父类是谁?异常分为几类? 父类:Exception。 异常分为两类:编译时异常、运行时异常 编译时异常和运行时异常的区别? 编译时异常:没有继承RuntimeException的异常&#xff0c;直接继承于Exception。 编译阶段就会…

阿里面试题二

实在是太长了 重新开一篇吧 dubbo 服务暴露 Dubbo——服务调用、服务暴露、服务引用过程 - 简书 这两篇文章写的是极好 我现在查得资源强的可怕朋友们 服务降级 MockClusterInvoker 负载均衡策略 容错机制在哪里实现的源码 通信 NIO、BIO区别&#xff0c;NIO解决了什么…

[C语言]——柔性数组

目录 一.柔性数组的特点 二.柔性数组的使用 三.柔性数组的优势 C99中&#xff0c;结构体中的最后⼀个元素允许是未知大小的数组&#xff0c;这就叫做『柔性数组』成员。 typedef struct st_type //typedef可以不写 { int i;int a[0];//柔性数组成员 }type_a; 有些编译器会…

jmeter压测websocket协议

一、jmeter 安装websocket插件 1、选项--插件管理 2、搜索WebSocket Samplers by Peter Doornbosch插件 进行安装 3、 重启 jmeter 二、jmeter压测websocket协议实战 2.1、以网站为例&#xff1a; websocket在线测试 1、断开连接 2、打开F12&#xff0c;查看WS数据 3、…

下班后开始更新进行做什么内容

今天没有完成的内容有哪些 作用插槽 没有完成 开始学习一下一个工具如何 学习一个kail 兼职挖漏洞的方式 先来安装一个windows镜像内容 安装成功了

蓝桥杯-阿坤老师的魔方挑战

图示: 代码: #include <iostream> using namespace std; int main() {int N,i,j,row,col,sum,max0;cin>>N;int ar[N][N];for(i0;i<N;i){for(j0;j<N;j){cin>>ar[i][j];}//输入矩阵 }for(i0;i<N;i){row0;coli;sum0;//重新初始化while(row<N){if(c…