Spring Data Redis
一、Redis 概述
Redis 数据结构
┌─────────────────────────────────────────────────────────────────┐
│ Redis 数据结构 │
├─────────────────────────────────────────────────────────────────┤
│ │
│ String(字符串) │
│ ┌──────────────────────────────────────────┐ │
│ │ SET name "张三" │ │
│ │ GET name → "张三" │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Hash(哈希) │
│ ┌──────────────────────────────────────────┐ │
│ │ HSET user:1 name "张三" age "18" │ │
│ │ HGET user:1 name → "张三" │ │
│ │ HGETALL user:1 → {name:"张三",age:"18"}│ │
│ └──────────────────────────────────────────┘ │
│ │
│ List(列表) │
│ ┌──────────────────────────────────────────┐ │
│ │ LPUSH list a b c → [c,b,a] │ │
│ │ LRANGE list 0 -1 → [c,b,a] │ │
│ └──────────────────────────────────────────┘ │
│ │
│ Set(集合) │
│ ┌──────────────────────────────────────────┐ │
│ │ SADD tags java spring vue │ │
│ │ SMEMBERS tags → {java,spring,vue} │ │
│ └──────────────────────────────────────────┘ │
│ │
│ ZSet(有序集合) │
│ ┌──────────────────────────────────────────┐ │
│ │ ZADD leaderboard 100 "张三" 90 "李四" │ │
│ │ ZREVRANGE leaderboard 0 9 → 按分数倒序│ │
│ └──────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘Spring Data Redis 模块
org.springframework.data.redis
├── spring-data-redis # 核心模块
├── lettuce-core # Redis 客户端(推荐)
├── jedis # 另一个客户端(较老)
└── spring-boot-starter-data-redis # Boot 自动配置二、快速开始
Maven 依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 可选:连接池 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>配置
yaml
spring:
redis:
host: localhost
port: 6379
password: # 可选
database: 0 # 默认数据库
timeout: 3000ms # 连接超时
# 连接池(Lettuce)
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1msRedisTemplate
java
@Service
public class RedisService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// String 操作
public void setString(String key, String value) {
redisTemplate.opsForValue().set(key, value);
}
public String getString(String key) {
return (String) redisTemplate.opsForValue().get(key);
}
// Hash 操作
public void setHash(String key, String field, String value) {
redisTemplate.opsForHash().put(key, field, value);
}
public Object getHash(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
// 设置过期
public void setWithExpire(String key, String value, long seconds) {
redisTemplate.opsForValue().set(key, value, Duration.ofSeconds(seconds));
}
}三、RedisTemplate 操作
StringRedisTemplate
java
@Service
public class StringRedisService {
@Autowired
private StringRedisTemplate stringRedisTemplate;
// 基本操作
public void set(String key, String value) {
stringRedisTemplate.opsForValue().set(key, value);
}
public String get(String key) {
return stringRedisTemplate.opsForValue().get(key);
}
// 过期
public void setEx(String key, String value, long seconds) {
stringRedisTemplate.opsForValue().set(key, value, Duration.ofSeconds(seconds));
}
// 计数
public void increment(String key) {
stringRedisTemplate.opsForValue().increment(key);
}
// 批量操作
public List<String> multiGet(List<String> keys) {
return stringRedisTemplate.opsForValue().multiGet(keys);
}
}Hash 操作
java
public class HashOps {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 存 Hash
public void hset(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
// 取 Hash 单个字段
public Object hget(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
// 取 Hash 所有字段
public Map<Object, Object> hgetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
// 批量存
public void hputAll(String key, Map<String, Object> map) {
redisTemplate.opsForHash().putAll(key, map);
}
// 删除字段
public void hdel(String key, String... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
// 判断字段存在
public Boolean hexists(String key, String field) {
return redisTemplate.opsForHash().hasKey(key, field);
}
}List 操作
java
public class ListOps {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 左 push
public void lpush(String key, Object... values) {
redisTemplate.opsForList().leftPushAll(key, values);
}
// 右 push
public void rpush(String key, Object... values) {
redisTemplate.opsForList().rightPushAll(key, values);
}
// 范围获取
public List<Object> lrange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
// 长度
public Long size(String key) {
return redisTemplate.opsForList().size(key);
}
// 左 pop
public Object lpop(String key) {
return redisTemplate.opsForList().leftPop(key);
}
}Set 操作
java
public class SetOps {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 添加
public void sadd(String key, Object... values) {
redisTemplate.opsForSet().add(key, values);
}
// 成员
public Set<Object> members(String key) {
return redisTemplate.opsForSet().members(key);
}
// 判断存在
public Boolean isMember(String key, Object value) {
return redisTemplate.opsForSet().isMember(key, value);
}
// 删除
public void srem(String key, Object... values) {
redisTemplate.opsForSet().remove(key, values);
}
}ZSet 操作
java
public class ZSetOps {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 添加(带分数)
public void zadd(String key, Object value, double score) {
redisTemplate.opsForZSet().add(key, value, score);
}
// 按分数范围获取(倒序)
public Set<Object> zrevrangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().reverseRangeByScore(key, min, max);
}
// 获取排名(倒序,0 是第一名)
public Long zrevrank(String key, Object value) {
return redisTemplate.opsForZSet().reverseRank(key, value);
}
// 获取分数
public Double zscore(String key, Object value) {
return redisTemplate.opsForZSet().score(key, value);
}
}四、序列化配置
默认序列化
java
// 默认使用 JdkSerializationRedisSerializer
// key 会出现 \xac\xed\x00\x05t\x00... 这样的前缀String 序列化(推荐)
java
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// Key 用 String 序列化
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
// Value 用 JSON 序列化
template.setValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
template.setHashValueSerializer(new Jackson2JsonRedisSerializer<>(Object.class));
template.afterPropertiesSet();
return template;
}
}通用序列化
java
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用 Jackson2JsonRedisSerializer
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper mapper = new ObjectMapper();
mapper.activateDefaultTyping(
mapper.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL,
JsonSerialize.Typing.STATIC
);
serializer.setObjectMapper(mapper);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
return template;
}五、Spring Cache 集成
启用缓存
java
@SpringBootApplication
@EnableCaching
public class Application { }配置缓存管理器
java
@Configuration
public class CacheConfig {
@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)) // 默认过期 1 小时
.serializeKeysWith( // Key 序列化
RedisSerializationContext.SerializationPair
.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith( // Value 序列化
RedisSerializationContext.SerializationPair
.fromSerializer(new GenericJackson2JsonRedisSerializer()))
.disableCachingNullValues(); // 不缓存 null
return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.withCacheConfiguration("users",
config.entryTtl(Duration.ofMinutes(30)))
.withCacheConfiguration("products",
config.entryTtl(Duration.ofHours(2)))
.build();
}
}@Cacheable 使用
java
@Service
@CacheConfig(cacheNames = "users")
public class UserService {
@Cacheable(key = "#id")
public User findById(Long id) {
return userRepository.findById(id).orElseThrow();
}
@Cacheable(key = "#name")
public User findByName(String name) {
return userRepository.findByName(name);
}
// 组合 key
@Cacheable(key = "#p0 + '-' + #p1")
public Object findByTypeAndStatus(int type, int status) {
return userRepository.findByTypeAndStatus(type, status);
}
}其他缓存注解
java
@Service
@CacheConfig(cacheNames = "users")
public class UserService {
// 存入缓存
@CachePut(key = "#user.id")
public User save(User user) {
return userRepository.save(user);
}
// 清除缓存
@CacheEvict(key = "#id")
public void delete(Long id) {
userRepository.deleteById(id);
}
// 清除所有缓存
@CacheEvict(allEntries = true)
public void deleteAll() {
userRepository.deleteAll();
}
}六、应用场景
1. 缓存
java
@Service
public class ProductService {
private static final String CACHE_KEY = "product";
private static final Duration CACHE_TTL = Duration.ofHours(2);
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public Product findById(Long id) {
String key = CACHE_KEY + ":" + id;
// 先查缓存
Product cached = (Product) redisTemplate.opsForValue().get(key);
if (cached != null) {
return cached;
}
// 缓存没有,查数据库
Product product = productRepository.findById(id).orElseThrow();
// 存入缓存
redisTemplate.opsForValue().set(key, product, CACHE_TTL);
return product;
}
@CacheEvict(key = "#id")
public Product update(Long id, Product product) {
product.setId(id);
return productRepository.save(product);
}
}2. 分布式锁
java
@Service
public class DistributedLockService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String LOCK_PREFIX = "lock:";
public boolean tryLock(String key, String value, long expireSeconds) {
String lockKey = LOCK_PREFIX + key;
Boolean result = redisTemplate.opsForValue()
.setIfAbsent(lockKey, value, Duration.ofSeconds(expireSeconds));
return Boolean.TRUE.equals(result);
}
public void unlock(String key, String value) {
String lockKey = LOCK_PREFIX + key;
// 只能释放自己的锁
Object currentValue = redisTemplate.opsForValue().get(lockKey);
if (value.equals(currentValue)) {
redisTemplate.delete(lockKey);
}
}
// 使用示例
public void seckill(Long productId, Long userId) {
String lockKey = "seckill:" + productId;
String lockValue = userId.toString();
try {
if (tryLock(lockKey, lockValue, 10)) {
// 扣减库存
productService.reduceStock(productId, 1);
} else {
throw new RuntimeException("系统繁忙,请重试");
}
} finally {
unlock(lockKey, lockValue);
}
}
}3. 计数器
java
@Service
public class CounterService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// 点赞数
public void like(Long postId) {
String key = "post:likes:" + postId;
redisTemplate.opsForValue().increment(key);
}
public void unlike(Long postId) {
String key = "post:likes:" + postId;
redisTemplate.opsForValue().decrement(key);
}
public Long getLikes(Long postId) {
String key = "post:likes:" + postId;
Object value = redisTemplate.opsForValue().get(key);
return value == null ? 0L : (Long) value;
}
// UV 统计
public void recordUV(String date, String userId) {
String key = "uv:" + date;
redisTemplate.opsForSet().add(key, userId);
}
public Long getUV(String date) {
String key = "uv:" + date;
return redisTemplate.opsForSet().size(key);
}
}4. Session 共享
java
@Configuration
@EnableRedisHttpSession(maxInactiveIntervalInSeconds = 3600)
public class RedisSessionConfig { }
// 或在 application.yml
spring:
session:
store-type: redis
redis:
namespace: myapp:session
flush-mode: on_save
save-mode: always七、Redis 工具类
完整工具类
java
@Component
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
// Key 操作
public Boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
public void delete(String key) {
redisTemplate.delete(key);
}
public void expire(String key, long seconds) {
redisTemplate.expire(key, seconds, TimeUnit.SECONDS);
}
// String
public void set(String key, Object value) {
redisTemplate.opsForValue().set(key, value);
}
public void set(String key, Object value, Duration duration) {
redisTemplate.opsForValue().set(key, value, duration);
}
public Object get(String key) {
return redisTemplate.opsForValue().get(key);
}
public <T> T get(String key, Class<T> clazz) {
Object value = redisTemplate.opsForValue().get(key);
if (value == null) return null;
return clazz.cast(value);
}
// Hash
public void hset(String key, String field, Object value) {
redisTemplate.opsForHash().put(key, field, value);
}
public Object hget(String key, String field) {
return redisTemplate.opsForHash().get(key, field);
}
public Map<Object, Object> hgetAll(String key) {
return redisTemplate.opsForHash().entries(key);
}
public void hdel(String key, String... fields) {
redisTemplate.opsForHash().delete(key, fields);
}
// List
public Long lpush(String key, Object... values) {
return redisTemplate.opsForList().leftPushAll(key, values);
}
public List<Object> lrange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
// Set
public Long sadd(String key, Object... values) {
return redisTemplate.opsForSet().add(key, values);
}
public Set<Object> smembers(String key) {
return redisTemplate.opsForSet().members(key);
}
// ZSet
public Boolean zadd(String key, Object value, double score) {
return redisTemplate.opsForZSet().add(key, value, score);
}
public Set<Object> zrangeByScore(String key, double min, double max) {
return redisTemplate.opsForZSet().rangeByScore(key, min, max);
}
}八、面试高频问题
Q1: RedisTemplate 和 StringRedisTemplate 区别?
StringRedisTemplate:Key 和 Value 都用 String 序列化RedisTemplate:Key 用 String,Value 用 JDK 序列化
Q2: 如何解决缓存穿透?
- 缓存空值(NULL)
- 布隆过滤器
Q3: 如何解决缓存雪崩?
- 过期时间加随机值
- 多级缓存
- 限流降级
Q4: Redis 和 Memcached 区别?
| 对比 | Redis | Memcached |
|---|---|---|
| 数据结构 | String/Hash/List/Set/ZSet | 仅 String |
| 持久化 | 支持 | 不支持 |
| 集群 | 原生支持 Cluster | 第三方 |
| 内存分配 | 自有实现 | slab |
Q5: @Cacheable 能不能缓存 null?
默认不能。配置 cacheNullValues: true 可以缓存 null
九、下一章预告
下一章我们将学习 事务管理:
- @Transactional 用法
- 事务传播行为
- 隔离级别
- 嵌套事务与回滚