java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > springboot redis配置

SpringBoot18 redis的配置方法

作者:ruleslol

本文介绍在SpringBoot项目中集成和使用Redis的方法,包括添加依赖、配置文件、自定义序列化方式、使用方式、实际使用示例、常见操作总结以及注意事项,特别强调了配置类的作用,即使用JSON序列化以提高数据的可读性、节省空间和跨语言兼容性,感兴趣的朋友跟随小编一起看看吧

一、Spring Boot 中使用 Redis

我来详细介绍如何在 Spring Boot 项目中集成和使用 Redis。

1. 添加依赖

pom.xml 中添加 Spring Data Redis 依赖:

<dependencies>
    <!-- Spring Boot Redis 依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <!-- Lettuce 连接池(可选,推荐) -->
    <dependency>
        <groupId>org.apache.commons</groupId>
        <artifactId>commons-pool2</artifactId>
    </dependency>
    <!-- 如果需要使用 JSON 序列化 -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

2. 配置文件

application.ymlapplication.properties 中配置 Redis:

application.yml:

spring:
  redis:
    host: localhost
    port: 6379
    password: # 如果有密码就填写
    database: 0 # Redis 数据库索引(默认为0)
    timeout: 3000ms # 连接超时时间
    lettuce:
      pool:
        max-active: 8 # 连接池最大连接数
        max-idle: 8 # 连接池最大空闲连接数
        min-idle: 0 # 连接池最小空闲连接数
        max-wait: -1ms # 连接池最大阻塞等待时间

3. Redis 配置类(推荐)

创建配置类以自定义序列化方式:

package com.example.config;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
@Configuration
public class RedisConfig {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);
        // 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 value
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = 
            new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        // 使用 StringRedisSerializer 来序列化和反序列化 key
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key 采用 String 的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash 的 key 也采用 String 的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value 序列化方式采用 jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash 的 value 序列化方式采用 jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }
}

4. 使用方式

方式一:直接注入 RedisTemplate

package com.example.service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.concurrent.TimeUnit;
@Service
public class UserService {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    // 设置值
    public void setUser(String key, User user) {
        redisTemplate.opsForValue().set(key, user);
    }
    // 设置值并设置过期时间
    public void setUserWithExpire(String key, User user, long timeout) {
        redisTemplate.opsForValue().set(key, user, timeout, TimeUnit.SECONDS);
    }
    // 获取值
    public User getUser(String key) {
        return (User) redisTemplate.opsForValue().get(key);
    }
    // 删除
    public void deleteUser(String key) {
        redisTemplate.delete(key);
    }
    // 判断 key 是否存在
    public boolean hasKey(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }
    // 设置过期时间
    public void expire(String key, long timeout) {
        redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }
}

方式二:封装 Redis 工具类(推荐)

package com.example.util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import java.util.Collection;
import java.util.concurrent.TimeUnit;
@Component
public class RedisUtil {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    // ========== String 操作 ==========
    /**
     * 设置缓存
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }
    /**
     * 设置缓存并设置过期时间
     */
    public void set(String key, Object value, long timeout, TimeUnit unit) {
        redisTemplate.opsForValue().set(key, value, timeout, unit);
    }
    /**
     * 获取缓存
     */
    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
    /**
     * 删除缓存
     */
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }
    /**
     * 批量删除
     */
    public Long delete(Collection<String> keys) {
        return redisTemplate.delete(keys);
    }
    /**
     * 设置过期时间
     */
    public Boolean expire(String key, long timeout, TimeUnit unit) {
        return redisTemplate.expire(key, timeout, unit);
    }
    /**
     * 判断 key 是否存在
     */
    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }
    /**
     * 递增
     */
    public Long increment(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }
    /**
     * 递减
     */
    public Long decrement(String key, long delta) {
        return redisTemplate.opsForValue().decrement(key, delta);
    }
    // ========== Hash 操作 ==========
    /**
     * Hash 设置
     */
    public void hSet(String key, String hashKey, Object value) {
        redisTemplate.opsForHash().put(key, hashKey, value);
    }
    /**
     * Hash 获取
     */
    public Object hGet(String key, String hashKey) {
        return redisTemplate.opsForHash().get(key, hashKey);
    }
    /**
     * Hash 删除
     */
    public Long hDelete(String key, Object... hashKeys) {
        return redisTemplate.opsForHash().delete(key, hashKeys);
    }
    // ========== List 操作 ==========
    /**
     * List 右侧推入
     */
    public Long lPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }
    /**
     * List 左侧弹出
     */
    public Object lPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }
    // ========== Set 操作 ==========
    /**
     * Set 添加
     */
    public Long sAdd(String key, Object... values) {
        return redisTemplate.opsForSet().add(key, values);
    }
    /**
     * Set 移除
     */
    public Long sRemove(String key, Object... values) {
        return redisTemplate.opsForSet().remove(key, values);
    }
    // ========== Sorted Set 操作 ==========
    /**
     * ZSet 添加
     */
    public Boolean zAdd(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }
}

5. 实际使用示例

@RestController
@RequestMapping("/api/user")
public class UserController {
    @Autowired
    private RedisUtil redisUtil;
    // 缓存用户信息
    @GetMapping("/{id}")
    public User getUser(@PathVariable Long id) {
        String key = "user:" + id;
        // 先从缓存中获取
        User user = (User) redisUtil.get(key);
        if (user == null) {
            // 缓存未命中,从数据库查询
            user = userService.getUserById(id);
            if (user != null) {
                // 存入缓存,过期时间 30 分钟
                redisUtil.set(key, user, 30, TimeUnit.MINUTES);
            }
        }
        return user;
    }
    // 更新用户时删除缓存
    @PutMapping("/{id}")
    public void updateUser(@PathVariable Long id, @RequestBody User user) {
        userService.updateUser(user);
        // 删除缓存
        redisUtil.delete("user:" + id);
    }
}

6. 常见操作总结

数据类型RedisTemplate 方法说明
StringopsForValue()字符串操作
HashopsForHash()哈希表操作
ListopsForList()列表操作
SetopsForSet()集合操作
Sorted SetopsForZSet()有序集合操作

7. 注意事项

  1. 序列化问题:默认使用 JDK 序列化,建议使用 JSON 序列化(如上面配置类所示)
  2. 缓存穿透:查询不存在的数据,建议缓存空值或使用布隆过滤器
  3. 缓存雪崩:大量缓存同时失效,建议设置随机过期时间
  4. 缓存击穿:热点数据过期,建议使用互斥锁或永不过期
  5. 连接池配置:根据实际并发量调整连接池参数

二、为什么要编写redis配置类呢

1. 可以直接使用,但有问题

引入依赖后可以直接 @Autowired 注入使用,Spring Boot 会自动配置一个默认的 RedisTemplate。

@Autowired
private RedisTemplate redisTemplate; // 可以直接用

但是,默认的 RedisTemplate 有一个严重问题:序列化方式!

2. 默认序列化的问题

Spring Boot 默认的 RedisTemplate 使用 JDK 序列化(JdkSerializationRedisSerializer),会导致:

问题演示:

// 存入数据
redisTemplate.opsForValue().set("user:1", user);

在 Redis 中看到的数据是这样的:

Key: "\xac\xed\x00\x05t\x00\x06user:1"
Value: "\xac\xed\x00\x05sr\x00\x1ccom.example.entity.User..."

问题有哪些?

  1. 不可读:存储的是二进制数据,无法直接在 Redis 客户端查看
  2. 占用空间大:JDK 序列化后的数据比 JSON 大很多
  3. 跨语言不兼容:其他语言(如 Python、Go)无法读取 Java 序列化的数据
  4. 安全风险:JDK 序列化存在已知的安全漏洞

对比:使用 JSON 序列化

Key: "user:1"
Value: {"id":1,"name":"张三","age":25}

这样就:

3. 为什么是 RedisTemplate<String, Object>?

泛型说明

RedisTemplate<K, V>
// K: Key 的类型
// V: Value 的类型

为什么用<String, Object>?

Key 使用 String:

// Redis 的 key 通常都是字符串
"user:1"
"product:100"
"cache:article:20"

Value 使用 Object:

// 可以存储各种类型的对象
redisTemplate.opsForValue().set("user:1", userObject);      // User 对象
redisTemplate.opsForValue().set("count", 100);              // Integer
redisTemplate.opsForValue().set("list", Arrays.asList(1,2,3)); // List

使用 Object 类型最灵活,可以存储任何对象。

4. 配置类做了什么?

让我简化说明配置类的作用:

@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(factory);
    // 关键:修改序列化方式
    // 1. Key 使用 String 序列化
    template.setKeySerializer(new StringRedisSerializer());
    // 2. Value 使用 JSON 序列化
    template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
    // 3. Hash 的 Key 和 Value 也设置序列化方式
    template.setHashKeySerializer(new StringRedisSerializer());
    template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
    return template;
}

核心作用就是:把默认的 JDK 序列化改成 JSON 序列化!

5. 不配置 vs 配置的对比

场景:存储用户对象

User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);

不配置(使用默认)

Redis 中存储的内容:

127.0.0.1:6379> keys *
1) "\xac\xed\x00\x05t\x00\x06user:1"
127.0.0.1:6379> get "\xac\xed\x00\x05t\x00\x06user:1"
"\xac\xed\x00\x05sr\x00\x1c..." # 一堆乱码

配置后(使用 JSON)

Redis 中存储的内容:

127.0.0.1:6379> keys *
1) "user:1"
127.0.0.1:6379> get user:1
"{\"id\":1,\"name\":\"张三\",\"age\":25}"

是不是清晰多了?

总结

  1. 可以不配置,但会用 JDK 序列化,导致数据不可读
  2. 配置的目的:改用 JSON 序列化,让数据可读、节省空间、跨语言兼容
  3. <String, Object>:Key 用字符串,Value 用 Object 更灵活
  4. 推荐做法:写配置类,统一使用 JSON 序列化

三、配置类中的ObjectMapper的相关操作

redis配置类中有一段代码:

ObjectMapper objectMapper = new ObjectMapper(); 
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); 
objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, 
ObjectMapper.DefaultTyping.NON_FINAL ); 
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

让我逐行解释这段代码的作用。

这段代码在解决什么问题?

核心问题:反序列化时的类型丢失

当你从 Redis 取出数据时,Jackson 不知道原始对象是什么类型,只能反序列化成 LinkedHashMap 或其他通用类型。

问题演示

// 存入 User 对象
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);
// 取出时
Object obj = redisTemplate.opsForValue().get("user:1");
System.out.println(obj.getClass()); 
// 输出:class java.util.LinkedHashMap(不是 User!)
// 无法直接使用
User user = (User) obj; // 报错:ClassCastException

逐行解释

1. 创建 ObjectMapper

ObjectMapper objectMapper = new ObjectMapper();

这是 Jackson 的核心类,负责 Java 对象和 JSON 之间的转换。

2. 设置可见性

objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);

作用:告诉 Jackson 可以访问对象的所有属性

PropertyAccessor.ALL 包括:

JsonAutoDetect.Visibility.ANY 表示:

示例:
public class User {
    private Long id;        // private 字段
    private String name;    // private 字段
    // 没有 getter/setter 也能序列化!
}

不设置这个配置的话:

// 默认只能访问 public 字段或有 getter/setter 的字段
// private 字段没有 getter 就无法序列化

3. 激活默认类型信息(重点!)

objectMapper.activateDefaultTyping(
    LaissezFaireSubTypeValidator.instance,
    ObjectMapper.DefaultTyping.NON_FINAL
);

这是最关键的配置!作用:在 JSON 中存储类型信息

不配置时的问题:

存入 Redis:

{
  "id": 1,
  "name": "张三",
  "age": 25
}

从 Redis 取出:

Object obj = redisTemplate.opsForValue().get("user:1");
// obj 是 LinkedHashMap,不是 User!
// 因为 Jackson 不知道原始类型是什么
配置后的效果:

存入 Redis(包含类型信息):

[
  "com.example.entity.User",
  {
    "id": 1,
    "name": "张三",
    "age": 25
  }
]

从 Redis 取出:

Object obj = redisTemplate.opsForValue().get("user:1");
// obj 就是 User 类型!可以直接转换
User user = (User) obj; // ✅ 成功
参数说明:

LaissezFaireSubTypeValidator.instance

ObjectMapper.DefaultTyping.NON_FINAL

4. 设置到序列化器

jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

把配置好的 ObjectMapper 设置给 Jackson 序列化器,让它使用我们的配置。

完整效果对比

配置前(类型丢失)

// 存入
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);
// Redis 中存储:
{
  "id": 1,
  "name": "张三",
  "age": 25
}
// 取出
Object obj = redisTemplate.opsForValue().get("user:1");
System.out.println(obj.getClass());
// 输出:class java.util.LinkedHashMap ❌
User user = (User) obj; // 报错!ClassCastException

配置后(保留类型)

// 存入
User user = new User(1L, "张三", 25);
redisTemplate.opsForValue().set("user:1", user);
// Redis 中存储:
[
  "com.example.entity.User",
  {
    "id": 1,
    "name": "张三",
    "age": 25
  }
]
// 取出
Object obj = redisTemplate.opsForValue().get("user:1");
System.out.println(obj.getClass());
// 输出:class com.example.entity.User ✅
User user = (User) obj; // 成功!

代码简化版(帮助理解)

ObjectMapper objectMapper = new ObjectMapper();
// 1. 让 Jackson 能访问 private 字段
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 2. 在 JSON 中保存类型信息(最重要!)
objectMapper.activateDefaultTyping(
    LaissezFaireSubTypeValidator.instance,  // 验证器:允许所有类型
    ObjectMapper.DefaultTyping.NON_FINAL    // 为非 final 类添加类型信息
);
// 3. 应用这些配置
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

注意事项

1. 安全风险

// 使用 LaissezFaireSubTypeValidator 可能有安全风险
// 因为它允许反序列化任何类型
// 生产环境可以考虑更严格的验证器

2. 性能影响

类型信息会增加存储空间
// 原始:{"id":1,"name":"张三"}
// 带类型:["com.example.User",{"id":1,"name":"张三"}]

3. 替代方案

如果不想使用类型信息,可以手动指定类型:

// 存入时就明确类型
ValueOperations<String, User> ops = redisTemplate.opsForValue();
ops.set("user:1", user);
// 取出时也明确类型
User user = ops.get("user:1");

总结

这段代码的核心作用:

  1. setVisibility:让 Jackson 能访问 private 字段
  2. activateDefaultTyping在 JSON 中存储类型信息(最重要!)
  3. 目的:解决反序列化时类型丢失的问题

不配置:取出来是 LinkedHashMap
配置后:取出来是原始的 User 对象

到此这篇关于SpringBoot18 redis的配置方法的文章就介绍到这了,更多相关springboot redis配置内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文