当前位置: 首页 > news >正文

SpringBoot18-redis的配置

一、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: localhostport: 6379password: # 如果有密码就填写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 {@Beanpublic RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {RedisTemplate<String, Object> template = new RedisTemplate<>();template.setConnectionFactory(connectionFactory);// 使用 Jackson2JsonRedisSerializer 来序列化和反序列化 valueJackson2JsonRedisSerializer<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 来序列化和反序列化 keyStringRedisSerializer stringRedisSerializer = new StringRedisSerializer();// key 采用 String 的序列化方式template.setKeySerializer(stringRedisSerializer);// hash 的 key 也采用 String 的序列化方式template.setHashKeySerializer(stringRedisSerializer);// value 序列化方式采用 jacksontemplate.setValueSerializer(jackson2JsonRedisSerializer);// hash 的 value 序列化方式采用 jacksontemplate.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 {@Autowiredprivate 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 {@Autowiredprivate 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 {@Autowiredprivate 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 包括:

  • FIELD(字段)
  • GETTER(get 方法)
  • SETTER(set 方法)
  • CREATOR(构造方法)
  • IS_GETTER(is 方法)

JsonAutoDetect.Visibility.ANY 表示:

  • public 可以访问
  • protected 可以访问
  • private 也可以访问 ⬅️ 关键
示例:
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

  • 一个宽松的类型验证器
  • 允许反序列化几乎所有类型
  • LaissezFaire 是法语,意思是"放任自由"

ObjectMapper.DefaultTyping.NON_FINAL

  • 非 final 类添加类型信息
  • 选项包括:
    • JAVA_LANG_OBJECT:只对 Object 类型
    • OBJECT_AND_NON_CONCRETE:Object 和抽象类/接口
    • NON_CONCRETE_AND_ARRAYS:抽象类、接口和数组
    • NON_FINAL:所有非 final 类 ⬅️ 最常用
    • EVERYTHING:所有类型

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 对象

http://www.dtcms.com/a/613235.html

相关文章:

  • PHP 表单 - 必需字段
  • python爬虫入门案例day05:Pexels
  • android studio Gradle 打包任务配置
  • 【AI学习-comfyUI学习-1批量抠图换背景工作流+2视频抠图工作流-各个部分学习-第十节】
  • Redis(124)Redis在电商系统中的应用有哪些?
  • [Dify 实战案例] 用 Dify 做一个多语种文档翻译工具:支持 TXT / DOCX / XLSX / PPTX 全格式
  • 自然语言编程:从一段Perl程序说起
  • OpenAI Whisper:技术、实战、生态
  • 【ZeroRange WebRTC】DTLS(Datagram Transport Layer Security)技术深度分析
  • 南京本地网站合肥建网站要多少钱
  • 从丝印判断ESP32-WROOM-32E模组Flash容量
  • react 学习
  • 语言模型(Language Model)介绍
  • 基于协同过滤推荐算法的求职招聘推荐系统u1ydn3f4(程序、源码、数据库、调试部署方案及开发环境)系统界面展示及获取方式置于文档末尾,可供参考。
  • 在 Ubuntu 中把系统字符集从英文改成支持中文
  • PyTorch实战:从零搭建CV模型
  • 网站权重不够高 导致友情链接批量查询
  • 如何在校园网让虚拟机联网并固定IP
  • 5. Qt深入 线程例子
  • 虚拟服务器和如何创建网站网站 注册模块怎么做
  • 【Linux日新月异(二)】CentOS 7用户与用户组管理深度指南:保障系统安全的基石
  • 大模型-提示工程
  • ARM编译器深度解析:从Keil到VSCode的STM32开发之
  • 支持CAS身份认证,支持接入Oracle11数据源,SQLBot开源智能问数系统v1.3.0版本发布
  • oracle官网在线免费测试环境 oracle live
  • 网站风格设计描述怎么样可以做网站充值代理
  • MySql的慢查询(慢日志)
  • 建设京东商城网站导航网站教程
  • 【MySQL】深度解析 Redo Log 与灾后恢复
  • 网站都需要续费吗绍兴以往网站招工做