一、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.yml 或 application.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 方法 | 说明 |
|---|---|---|
| string | opsforvalue() | 字符串操作 |
| hash | opsforhash() | 哈希表操作 |
| list | opsforlist() | 列表操作 |
| set | opsforset() | 集合操作 |
| sorted set | opsforzset() | 有序集合操作 |
7. 注意事项
- 序列化问题:默认使用 jdk 序列化,建议使用 json 序列化(如上面配置类所示)
- 缓存穿透:查询不存在的数据,建议缓存空值或使用布隆过滤器
- 缓存雪崩:大量缓存同时失效,建议设置随机过期时间
- 缓存击穿:热点数据过期,建议使用互斥锁或永不过期
- 连接池配置:根据实际并发量调整连接池参数
二、为什么要编写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..."
问题有哪些?
- 不可读:存储的是二进制数据,无法直接在 redis 客户端查看
- 占用空间大:jdk 序列化后的数据比 json 大很多
- 跨语言不兼容:其他语言(如 python、go)无法读取 java 序列化的数据
- 安全风险: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}"
是不是清晰多了?
总结
- 可以不配置,但会用 jdk 序列化,导致数据不可读
- 配置的目的:改用 json 序列化,让数据可读、节省空间、跨语言兼容
<string, object>:key 用字符串,value 用 object 更灵活- 推荐做法:写配置类,统一使用 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");总结
这段代码的核心作用:
- setvisibility:让 jackson 能访问 private 字段
- activatedefaulttyping:在 json 中存储类型信息(最重要!)
- 目的:解决反序列化时类型丢失的问题
不配置:取出来是 linkedhashmap
配置后:取出来是原始的 user 对象
到此这篇关于springboot18 redis的配置方法的文章就介绍到这了,更多相关springboot redis配置内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论