强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Redis 完全指南 / 18 - Spring Data Redis

Spring Data Redis

18.1 Spring Data Redis 概述

Spring Data Redis 是 Spring 官方提供的 Redis 集成框架,封装了底层的 Redis 客户端操作。

支持的客户端

客户端特点状态
Lettuce基于 Netty,线程安全,支持响应式默认(推荐)
Jedis简单直觉,连接池管理可选

Maven 依赖

<dependencies>
    <!-- Spring Boot Starter Data Redis -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    
    <!-- 连接池(Lettuce 默认支持,Jedis 需要额外引入) -->
    <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>

18.2 基础配置

application.yml

spring:
  data:
    redis:
      host: localhost
      port: 6379
      password: yourpassword
      database: 0
      timeout: 5000ms
      # Lettuce 连接池配置
      lettuce:
        pool:
          max-active: 100      # 最大连接数
          max-idle: 20         # 最大空闲连接
          min-idle: 5          # 最小空闲连接
          max-wait: 3000ms     # 获取连接最大等待时间
        shutdown-timeout: 200ms
      # Sentinel 配置
      # sentinel:
      #   master: mymaster
      #   nodes:
      #     - 192.168.1.1:26379
      #     - 192.168.1.2:26379
      #     - 192.168.1.3:26379
      # Cluster 配置
      # cluster:
      #   nodes:
      #     - 192.168.1.1:6379
      #     - 192.168.1.2:6379
      #     - 192.168.1.3:6379
      #   max-redirects: 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 com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.annotation.EnableCaching;
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.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

@Configuration
@EnableCaching
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(factory);

        // Key 序列化
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);

        // Value 序列化
        GenericJackson2JsonRedisSerializer jsonSerializer = createJsonSerializer();
        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);

        template.afterPropertiesSet();
        return template;
    }

    private GenericJackson2JsonRedisSerializer createJsonSerializer() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        mapper.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        mapper.registerModule(new JavaTimeModule());
        return new GenericJackson2JsonRedisSerializer(mapper);
    }
}

18.3 RedisTemplate 基本操作

注入和使用

package com.example.service;

import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.data.redis.core.HashOperations;
import org.springframework.data.redis.core.ListOperations;
import org.springframework.data.redis.core.SetOperations;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.*;
import java.util.concurrent.TimeUnit;

@Service
public class RedisService {

    @Resource
    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 Boolean setIfAbsent(String key, Object value, long timeout, TimeUnit unit) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, timeout, unit);
    }

    /**
     * 获取值
     */
    @SuppressWarnings("unchecked")
    public <T> T get(String key) {
        return (T) redisTemplate.opsForValue().get(key);
    }

    /**
     * 自增
     */
    public Long increment(String key) {
        return redisTemplate.opsForValue().increment(key);
    }

    public Long increment(String key, long delta) {
        return redisTemplate.opsForValue().increment(key, delta);
    }

    // ========== 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 Long hIncrement(String key, String field, long delta) {
        return redisTemplate.opsForHash().increment(key, field, delta);
    }

    public void hDelete(String key, Object... fields) {
        redisTemplate.opsForHash().delete(key, fields);
    }

    // ========== List 操作 ==========

    public Long lPush(String key, Object value) {
        return redisTemplate.opsForList().leftPush(key, value);
    }

    public Long rPush(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    public Object lPop(String key) {
        return redisTemplate.opsForList().leftPop(key);
    }

    public Object rPop(String key) {
        return redisTemplate.opsForList().rightPop(key);
    }

    public List<Object> lRange(String key, long start, long end) {
        return redisTemplate.opsForList().range(key, start, end);
    }

    public Long lLen(String key) {
        return redisTemplate.opsForList().size(key);
    }

    // ========== 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);
    }

    public Boolean sIsMember(String key, Object value) {
        return redisTemplate.opsForSet().isMember(key, value);
    }

    public Long sSize(String key) {
        return redisTemplate.opsForSet().size(key);
    }

    // ========== ZSet 操作 ==========

    public Boolean zAdd(String key, Object value, double score) {
        return redisTemplate.opsForZSet().add(key, value, score);
    }

    public Set<Object> zRange(String key, long start, long end) {
        return redisTemplate.opsForZSet().range(key, start, end);
    }

    public Set<ZSetOperations.TypedTuple<Object>> zRangeWithScores(String key, long start, long end) {
        return redisTemplate.opsForZSet().rangeWithScores(key, start, end);
    }

    public Long zRank(String key, Object value) {
        return redisTemplate.opsForZSet().rank(key, value);
    }

    public Double zIncrementScore(String key, Object value, double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, value, delta);
    }

    // ========== 通用操作 ==========

    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);
    }

    public Long getExpire(String key) {
        return redisTemplate.getExpire(key);
    }

    public Boolean hasKey(String key) {
        return redisTemplate.hasKey(key);
    }

    // ========== Pipeline 批量操作 ==========

    public List<Object> batchGet(List<String> keys) {
        return redisTemplate.executePipelined(connection -> {
            for (String key : keys) {
                connection.stringCommands().get(key.getBytes());
            }
            return null;
        });
    }

    // ========== Lua 脚本执行 ==========

    public Object executeScript(String script, List<String> keys, Object... args) {
        return redisTemplate.execute(
            new org.springframework.data.redis.core.script.DefaultRedisScript<>(script, Long.class),
            keys,
            args
        );
    }
}

18.4 缓存注解

启用缓存

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory factory) {
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(30))
            .serializeKeysWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new StringRedisSerializer()))
            .serializeValuesWith(RedisSerializationContext.SerializationPair
                .fromSerializer(new GenericJackson2JsonRedisSerializer()))
            .disableCachingNullValues();
        
        return RedisCacheManager.builder(factory)
            .cacheDefaults(config)
            .withCacheConfiguration("user", 
                RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)))
            .withCacheConfiguration("product", 
                RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)))
            .transactionAware()
            .build();
    }
}

缓存注解使用

package com.example.service;

import com.example.entity.User;
import com.example.repository.UserRepository;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;

@Service
@CacheConfig(cacheNames = "user")
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    /**
     * 缓存查询结果
     */
    @Cacheable(key = "#userId")
    public User getUserById(Long userId) {
        System.out.println("Querying database for user: " + userId);
        return userRepository.findById(userId).orElse(null);
    }

    /**
     * 条件缓存(只缓存活跃用户)
     */
    @Cacheable(key = "#userId", condition = "#result != null && #result.active")
    public User getActiveUser(Long userId) {
        return userRepository.findById(userId).orElse(null);
    }

    /**
     * 缓存结果为空时不缓存
     */
    @Cacheable(key = "#userId", unless = "#result == null")
    public User getUserOrNull(Long userId) {
        return userRepository.findById(userId).orElse(null);
    }

    /**
     * 更新时清除缓存
     */
    @CachePut(key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }

    /**
     * 删除时清除缓存
     */
    @CacheEvict(key = "#userId")
    public void deleteUser(Long userId) {
        userRepository.deleteById(userId);
    }

    /**
     * 清除所有用户缓存
     */
    @CacheEvict(allEntries = true)
    public void clearAllCache() {
        System.out.println("All user cache cleared");
    }

    /**
     * 先更新数据库,再删除缓存
     */
    @Caching(evict = {
        @CacheEvict(key = "#user.id"),
        @CacheEvict(key = "#user.email")
    })
    public User updateAndEvict(User user) {
        return userRepository.save(user);
    }
}

注解参数说明

注解说明常用参数
@Cacheable查询时缓存结果key, condition, unless, cacheNames
@CachePut更新缓存key, condition, unless
@CacheEvict删除缓存key, allEntries, beforeInvocation
@Caching组合多个注解cacheable, put, evict
@CacheConfig类级别缓存配置cacheNames, keyGenerator

SpEL 表达式

// 常用 SpEL 表达式
@Cacheable(key = "#id")                    // 方法参数
@Cacheable(key = "#user.id")               // 对象属性
@Cacheable(key = "#root.methodName")       // 方法名
@Cacheable(key = "#root.args[0]")          // 第一个参数
@Cacheable(key = "#root.target")           // 目标对象
@Cacheable(key = "T(java.lang.String).valueOf(#id)")  // 静态方法调用
@Cacheable(key = "'user:' + #id")          // 字符串拼接

18.5 分布式锁实现

package com.example.lock;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

@Component
public class RedisDistributedLock {

    private final StringRedisTemplate redisTemplate;
    
    private static final String LOCK_SCRIPT = 
        "if redis.call('get', KEYS[1]) == ARGV[1] then " +
        "  return redis.call('del', KEYS[1]) " +
        "else " +
        "  return 0 " +
        "end";
    
    private static final DefaultRedisScript<Long> UNLOCK_SCRIPT = 
        new DefaultRedisScript<>(LOCK_SCRIPT, Long.class);

    public RedisDistributedLock(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 加锁
     */
    public String tryLock(String lockKey, long timeout, TimeUnit unit) {
        String lockValue = UUID.randomUUID().toString();
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, lockValue, timeout, unit);
        return Boolean.TRUE.equals(success) ? lockValue : null;
    }

    /**
     * 解锁(Lua 脚本保证原子性)
     */
    public boolean unlock(String lockKey, String lockValue) {
        Long result = redisTemplate.execute(
            UNLOCK_SCRIPT,
            Collections.singletonList(lockKey),
            lockValue
        );
        return result != null && result > 0;
    }
}

// 使用示例
@Service
public class OrderService {

    @Autowired
    private RedisDistributedLock distributedLock;

    public void createOrder(Long userId, Long productId) {
        String lockKey = "lock:order:create:" + userId;
        String lockValue = distributedLock.tryLock(lockKey, 30, TimeUnit.SECONDS);
        
        if (lockValue == null) {
            throw new RuntimeException("系统繁忙,请稍后重试");
        }
        
        try {
            // 业务逻辑
            processOrder(userId, productId);
        } finally {
            distributedLock.unlock(lockKey, lockValue);
        }
    }
}

📌 业务场景

场景一:用户信息缓存

@Cacheable(key = "'user:' + #userId", cacheNames = "user", unless = "#result == null")
public User getUserById(Long userId) {
    return userRepository.findById(userId).orElse(null);
}

场景二:排行榜

public List<RankEntry> getTopRankings(String rankingKey, int topN) {
    Set<ZSetOperations.TypedTuple<Object>> tuples = 
        redisTemplate.opsForZSet().reverseRangeWithScores(rankingKey, 0, topN - 1);
    
    List<RankEntry> rankings = new ArrayList<>();
    int rank = 1;
    for (ZSetOperations.TypedTuple<Object> tuple : tuples) {
        rankings.add(new RankEntry(rank++, tuple.getValue(), tuple.getScore()));
    }
    return rankings;
}

场景三:秒杀库存扣减

private static final String STOCK_SCRIPT = 
    "local stock = tonumber(redis.call('GET', KEYS[1]) or '0') " +
    "if stock > 0 then " +
    "  redis.call('DECR', KEYS[1]) " +
    "  return 1 " +
    "end " +
    "return 0";

public boolean deductStock(String skuId) {
    DefaultRedisScript<Long> script = new DefaultRedisScript<>(STOCK_SCRIPT, Long.class);
    Long result = redisTemplate.execute(script, 
        Collections.singletonList("stock:" + skuId));
    return result != null && result > 0;
}

🔗 扩展阅读