SpringBoot18 redis的配置方法
一、Spring Boot 中使用 Redis
我來詳細(xì)介紹如何在 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.yml 或 application.properties 中配置 Redis:
application.yml:
spring:
redis:
host: localhost
port: 6379
password: # 如果有密碼就填寫
database: 0 # Redis 數(shù)據(jù)庫索引(默認(rèn)為0)
timeout: 3000ms # 連接超時時間
lettuce:
pool:
max-active: 8 # 連接池最大連接數(shù)
max-idle: 8 # 連接池最大空閑連接數(shù)
min-idle: 0 # 連接池最小空閑連接數(shù)
max-wait: -1ms # 連接池最大阻塞等待時間3. Redis 配置類(推薦)
創(chuàng)建配置類以自定義序列化方式:
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;
// 設(shè)置值
public void setUser(String key, User user) {
redisTemplate.opsForValue().set(key, user);
}
// 設(shè)置值并設(shè)置過期時間
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));
}
// 設(shè)置過期時間
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 操作 ==========
/**
* 設(shè)置緩存
*/
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
/**
* 設(shè)置緩存并設(shè)置過期時間
*/
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);
}
/**
* 設(shè)置過期時間
*/
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 設(shè)置
*/
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 右側(cè)推入
*/
public Long lPush(String key, Object value) {
return redisTemplate.opsForList().rightPush(key, value);
}
/**
* List 左側(cè)彈出
*/
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) {
// 緩存未命中,從數(shù)據(jù)庫查詢
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. 常見操作總結(jié)
| 數(shù)據(jù)類型 | RedisTemplate 方法 | 說明 |
|---|---|---|
| String | opsForValue() | 字符串操作 |
| Hash | opsForHash() | 哈希表操作 |
| List | opsForList() | 列表操作 |
| Set | opsForSet() | 集合操作 |
| Sorted Set | opsForZSet() | 有序集合操作 |
7. 注意事項
- 序列化問題:默認(rèn)使用 JDK 序列化,建議使用 JSON 序列化(如上面配置類所示)
- 緩存穿透:查詢不存在的數(shù)據(jù),建議緩存空值或使用布隆過濾器
- 緩存雪崩:大量緩存同時失效,建議設(shè)置隨機(jī)過期時間
- 緩存擊穿:熱點數(shù)據(jù)過期,建議使用互斥鎖或永不過期
- 連接池配置:根據(jù)實際并發(fā)量調(diào)整連接池參數(shù)
二、為什么要編寫redis配置類呢
1. 可以直接使用,但有問題
引入依賴后可以直接 @Autowired 注入使用,Spring Boot 會自動配置一個默認(rèn)的 RedisTemplate。
@Autowired private RedisTemplate redisTemplate; // 可以直接用
但是,默認(rèn)的 RedisTemplate 有一個嚴(yán)重問題:序列化方式!
2. 默認(rèn)序列化的問題
Spring Boot 默認(rèn)的 RedisTemplate 使用 JDK 序列化(JdkSerializationRedisSerializer),會導(dǎo)致:
問題演示:
// 存入數(shù)據(jù)
redisTemplate.opsForValue().set("user:1", user);
在 Redis 中看到的數(shù)據(jù)是這樣的:
Key: "\xac\xed\x00\x05t\x00\x06user:1"
Value: "\xac\xed\x00\x05sr\x00\x1ccom.example.entity.User..."
問題有哪些?
- 不可讀:存儲的是二進(jìn)制數(shù)據(jù),無法直接在 Redis 客戶端查看
- 占用空間大:JDK 序列化后的數(shù)據(jù)比 JSON 大很多
- 跨語言不兼容:其他語言(如 Python、Go)無法讀取 Java 序列化的數(shù)據(jù)
- 安全風(fēng)險:JDK 序列化存在已知的安全漏洞
對比:使用 JSON 序列化
Key: "user:1"
Value: {"id":1,"name":"張三","age":25}
這樣就:
- ? 可讀性強(qiáng)
- ? 體積更小
- ? 跨語言兼容
- ? 更安全
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);
// 關(guān)鍵:修改序列化方式
// 1. Key 使用 String 序列化
template.setKeySerializer(new StringRedisSerializer());
// 2. Value 使用 JSON 序列化
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
// 3. Hash 的 Key 和 Value 也設(shè)置序列化方式
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
return template;
}核心作用就是:把默認(rèn)的 JDK 序列化改成 JSON 序列化!
5. 不配置 vs 配置的對比
場景:存儲用戶對象
User user = new User(1L, "張三", 25);
redisTemplate.opsForValue().set("user:1", user);
不配置(使用默認(rèn))
Redis 中存儲的內(nèi)容:
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 中存儲的內(nèi)容:
127.0.0.1:6379> keys *
1) "user:1"
127.0.0.1:6379> get user:1
"{\"id\":1,\"name\":\"張三\",\"age\":25}"
是不是清晰多了?
總結(jié)
- 可以不配置,但會用 JDK 序列化,導(dǎo)致數(shù)據(jù)不可讀
- 配置的目的:改用 JSON 序列化,讓數(shù)據(jù)可讀、節(jié)省空間、跨語言兼容
<String, Object>:Key 用字符串,Value 用 Object 更靈活- 推薦做法:寫配置類,統(tǒng)一使用 JSON 序列化
三、配置類中的ObjectMapper的相關(guān)操作
redis配置類中有一段代碼:
ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); objectMapper.activateDefaultTyping( LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL ); jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
讓我逐行解釋這段代碼的作用。
這段代碼在解決什么問題?
核心問題:反序列化時的類型丟失
當(dāng)你從 Redis 取出數(shù)據(jù)時,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. 創(chuàng)建 ObjectMapper
ObjectMapper objectMapper = new ObjectMapper();
這是 Jackson 的核心類,負(fù)責(zé) Java 對象和 JSON 之間的轉(zhuǎn)換。
2. 設(shè)置可見性
objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
作用:告訴 Jackson 可以訪問對象的所有屬性
PropertyAccessor.ALL 包括:
FIELD(字段)GETTER(get 方法)SETTER(set 方法)CREATOR(構(gòu)造方法)IS_GETTER(is 方法)
JsonAutoDetect.Visibility.ANY 表示:
public可以訪問protected可以訪問private也可以訪問 ?? 關(guān)鍵
示例:
public class User {
private Long id; // private 字段
private String name; // private 字段
// 沒有 getter/setter 也能序列化!
}不設(shè)置這個配置的話:
// 默認(rèn)只能訪問 public 字段或有 getter/setter 的字段 // private 字段沒有 getter 就無法序列化
3. 激活默認(rèn)類型信息(重點?。?/h4>
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
這是最關(guān)鍵的配置!作用:在 JSON 中存儲類型信息
不配置時的問題:
objectMapper.activateDefaultTyping(
LaissezFaireSubTypeValidator.instance,
ObjectMapper.DefaultTyping.NON_FINAL
);
這是最關(guān)鍵的配置!作用:在 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 類型!可以直接轉(zhuǎn)換
User user = (User) obj; // ? 成功
參數(shù)說明:
LaissezFaireSubTypeValidator.instance
- 一個寬松的類型驗證器
- 允許反序列化幾乎所有類型
LaissezFaire是法語,意思是"放任自由"
ObjectMapper.DefaultTyping.NON_FINAL
- 為非 final 類添加類型信息
- 選項包括:
JAVA_LANG_OBJECT:只對 Object 類型OBJECT_AND_NON_CONCRETE:Object 和抽象類/接口NON_CONCRETE_AND_ARRAYS:抽象類、接口和數(shù)組NON_FINAL:所有非 final 類 ?? 最常用EVERYTHING:所有類型
4. 設(shè)置到序列化器
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
把配置好的 ObjectMapper 設(shè)置給 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. 應(yīng)用這些配置
jackson2JsonRedisSerializer.setObjectMapper(objectMapper);注意事項
1. 安全風(fēng)險
// 使用 LaissezFaireSubTypeValidator 可能有安全風(fēng)險
// 因為它允許反序列化任何類型
// 生產(chǎn)環(huán)境可以考慮更嚴(yán)格的驗證器
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");總結(jié)
這段代碼的核心作用:
- setVisibility:讓 Jackson 能訪問 private 字段
- activateDefaultTyping:在 JSON 中存儲類型信息(最重要?。?/li>
- 目的:解決反序列化時類型丟失的問題
不配置:取出來是 LinkedHashMap
配置后:取出來是原始的 User 對象
到此這篇關(guān)于SpringBoot18 redis的配置方法的文章就介紹到這了,更多相關(guān)springboot redis配置內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java調(diào)用DeepSeek?api實現(xiàn)方法記錄
這篇文章主要介紹了如何在Java中調(diào)用DeepSeek?API,包括在官網(wǎng)獲取APIKeys、創(chuàng)建API請求工具類以及處理返回結(jié)果的步驟,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-02-02
Redis監(jiān)聽過期的key實現(xiàn)流程詳解
本文主要介紹了Redis監(jiān)聽key的過期時間,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
MyBatis多表關(guān)聯(lián)查詢的實現(xiàn)示例
本文主要介紹了MyBatis多表關(guān)聯(lián)查詢的實現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-03-03
BeanUtils.copyProperties復(fù)制不生效的解決
這篇文章主要介紹了BeanUtils.copyProperties復(fù)制不生效的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
java針對于時間轉(zhuǎn)換的DateUtils工具類
這篇文章主要為大家詳細(xì)介紹了java針對于時間轉(zhuǎn)換的DateUtils工具類,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
專屬于程序員的浪漫-Java輸出動態(tài)閃圖iloveyou
這篇文章主要介紹了專屬于程序員的浪漫-Java輸出動態(tài)閃圖iloveyou,具有一定參考價值,需要的朋友可以了解下。2017-11-11
springboot整合mybatis-plus實現(xiàn)多表分頁查詢的示例代碼
這篇文章主要介紹了springboot整合mybatis-plus實現(xiàn)多表分頁查詢的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-03-03
IntelliJ IDEA中如何調(diào)試Java Stream操作
這篇文章主要介紹了IntelliJ IDEA中如何優(yōu)雅的調(diào)試Java Stream操作,在強(qiáng)大的IDEA插件支持下,stream的調(diào)試其實也沒那么難了,下面就來學(xué)習(xí)一下在IDEA中如何調(diào)試stream操作吧2022-05-05

