Skip to content

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: -1ms

RedisTemplate

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: 如何解决缓存穿透?

  1. 缓存空值(NULL)
  2. 布隆过滤器

Q3: 如何解决缓存雪崩?

  1. 过期时间加随机值
  2. 多级缓存
  3. 限流降级

Q4: Redis 和 Memcached 区别?

对比RedisMemcached
数据结构String/Hash/List/Set/ZSet仅 String
持久化支持不支持
集群原生支持 Cluster第三方
内存分配自有实现slab

Q5: @Cacheable 能不能缓存 null?

默认不能。配置 cacheNullValues: true 可以缓存 null

九、下一章预告

下一章我们将学习 事务管理

  • @Transactional 用法
  • 事务传播行为
  • 隔离级别
  • 嵌套事务与回滚

基于 MIT 许可发布