【Day57】SpringBoot 整合 Redis:吃透缓存配置与 API 实战

前言

在 Java Web 开发中,Redis 是高频使用的缓存中间件,它能极大提升系统性能、减轻数据库压力。今天这篇学习日记,我会带着大家从 0 到 1 实现 SpringBoot 整合 Redis,不仅会讲基础的配置和 API 使用,还会结合实战场景讲解缓存的核心用法,新手也能跟着敲完就用。

一、前置准备

1. 环境要求

  • Redis 服务:本地安装 Redis(推荐 5.x/6.x 版本)或使用云服务器 Redis
  • 测试工具:Redis Desktop Manager(可视化工具,可选)、Postman(接口测试)
  • SpringBoot 版本:2.7.12(和前文保持一致,避免版本兼容问题)

2. 核心依赖引入(pom.xml)

SpringBoot 提供了spring-boot-starter-data-redis依赖,能快速整合 Redis,无需手动引入繁杂的客户端依赖:

xml

XML 复制代码
<!-- Redis核心依赖 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖(提升Redis连接性能,必加) -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!-- lombok(简化代码,可选) -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <optional>true</optional>
</dependency>

二、核心配置:Redis 连接与自定义配置

1. 基础连接配置(application.yml)

src/main/resources下创建 / 修改application.yml,配置 Redis 连接信息:

yaml

复制代码
spring:
  # Redis配置
  redis:
    # 服务器地址(本地默认127.0.0.1)
    host: 127.0.0.1
    # 端口(默认6379)
    port: 6379
    # 密码(无密码则注释)
    # password: 123456
    # 数据库索引(Redis默认有16个库,从0开始)
    database: 0
    # 连接超时时间
    timeout: 10000ms
    # 连接池配置
    lettuce:
      pool:
        # 最大活跃连接数
        max-active: 8
        # 最大空闲连接数
        max-idle: 8
        # 最小空闲连接数
        min-idle: 0
        # 最大等待时间(-1表示无限制)
        max-wait: -1ms

2. 自定义 RedisTemplate 配置(关键)

SpringBoot 默认提供的RedisTemplate使用JdkSerializationRedisSerializer序列化,存到 Redis 的 key/value 会是乱码,因此需要自定义配置,改用Jackson2JsonRedisSerializer实现 JSON 序列化:

java

运行

java 复制代码
package com.java.diary.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
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 factory) {
        // 定义RedisTemplate对象
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 设置连接工厂
        redisTemplate.setConnectionFactory(factory);

        // 1. 创建JSON序列化器
        Jackson2JsonRedisSerializer<Object> jacksonSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 开启所有字段的序列化
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        // 支持JDK8日期类型
        om.findAndRegisterModules();
        jacksonSerializer.setObjectMapper(om);

        // 2. 创建String序列化器(key和hashKey用String)
        StringRedisSerializer stringSerializer = new StringRedisSerializer();

        // 3. 配置序列化规则
        redisTemplate.setKeySerializer(stringSerializer); // key序列化
        redisTemplate.setHashKeySerializer(stringSerializer); // hash的key序列化
        redisTemplate.setValueSerializer(jacksonSerializer); // value序列化
        redisTemplate.setHashValueSerializer(jacksonSerializer); // hash的value序列化
        redisTemplate.afterPropertiesSet();

        return redisTemplate;
    }
}

核心说明

  • StringRedisSerializer:保证 Redis 中的 key 是清晰的字符串,而非乱码;
  • Jackson2JsonRedisSerializer:将 Java 对象序列化为 JSON 字符串存储,方便查看和解析;
  • 配置后,Redis 中存储的内容可读性强,且能正确反序列化为 Java 对象。

三、核心实战:Redis API 全场景使用

SpringBoot 操作 Redis 主要有两种方式:

  1. RedisTemplate:通用型 API,支持所有 Redis 数据类型(String、Hash、List、Set、ZSet);
  2. StringRedisTemplate:简化版,专门处理 String 类型(底层也是 RedisTemplate,序列化方式固定为 String)。

1. 封装 Redis 工具类(企业级实战)

实际开发中,建议封装工具类统一管理 Redis 操作,避免重复代码:

java

运行

java 复制代码
package com.java.diary.util;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.concurrent.TimeUnit;

@Component
@RequiredArgsConstructor // 构造器注入(替代@Autowired)
public class RedisUtil {

    private final RedisTemplate<String, Object> redisTemplate;

    // ==================== String类型操作 ====================
    /**
     * 设置缓存
     * @param key 键
     * @param value 值
     * @param timeout 过期时间(秒)
     */
    public void set(String key, Object value, long timeout) {
        redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.SECONDS);
    }

    /**
     * 设置缓存(无过期时间)
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 获取缓存
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 删除缓存
     */
    public Boolean delete(String key) {
        return redisTemplate.delete(key);
    }

    /**
     * 设置过期时间
     */
    public Boolean expire(String key, long timeout) {
        return redisTemplate.expire(key, timeout, TimeUnit.SECONDS);
    }

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

2. 实体类准备(测试对象存储)

java

运行

java 复制代码
package com.java.diary.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.io.Serializable; // 必须实现序列化接口(Redis存储对象要求)

@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Serializable {
    private Integer id;
    private String name;
    private Integer age;
}

3. 实战测试:Controller 调用 Redis

java

运行

java 复制代码
package com.java.diary.controller;

import com.java.diary.entity.User;
import com.java.diary.util.RedisUtil;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@Slf4j
@RestController
@RequestMapping("/redis")
@RequiredArgsConstructor
public class RedisController {

    private final RedisUtil redisUtil;

    // 1. 测试String类型 - 存储普通字符串
    @GetMapping("/set/string")
    public Map<String, Object> setString() {
        redisUtil.set("test:str", "Hello Redis!", 60); // 60秒过期
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "字符串存储成功");
        return result;
    }

    // 2. 测试String类型 - 获取普通字符串
    @GetMapping("/get/string")
    public Map<String, Object> getString() {
        String value = (String) redisUtil.get("test:str");
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", value);
        return result;
    }

    // 3. 测试存储Java对象
    @PostMapping("/set/user")
    public Map<String, Object> setUser(@RequestBody User user) {
        String key = "test:user:" + user.getId();
        redisUtil.set(key, user, 300); // 5分钟过期
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("msg", "用户对象存储成功");
        return result;
    }

    // 4. 测试获取Java对象
    @GetMapping("/get/user/{id}")
    public Map<String, Object> getUser(@PathVariable Integer id) {
        String key = "test:user:" + id;
        User user = (User) redisUtil.get(key);
        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", user);
        return result;
    }

    // 5. 测试Hash类型
    @GetMapping("/test/hash")
    public Map<String, Object> testHash() {
        String key = "test:hash:user";
        redisUtil.hSet(key, "name", "李四");
        redisUtil.hSet(key, "age", 28);
        String name = (String) redisUtil.hGet(key, "name");
        Integer age = (Integer) redisUtil.hGet(key, "age");

        Map<String, Object> result = new HashMap<>();
        result.put("code", 200);
        result.put("data", "name=" + name + ", age=" + age);
        return result;
    }
}

4. 测试步骤

  1. 启动 Redis 服务(本地执行redis-server);
  2. 启动 SpringBoot 项目;
  3. 接口测试:
    • 存储字符串:http://localhost:8080/redis/set/string

    • 获取字符串:http://localhost:8080/redis/get/string

    • 存储用户对象:Postman 发送 POST 请求http://localhost:8080/redis/set/user,请求体:

      json

      复制代码
      {
        "id": 1,
        "name": "张三",
        "age": 25
      }
    • 获取用户对象:http://localhost:8080/redis/get/user/1

    • Hash 测试:http://localhost:8080/redis/test/hash

四、进阶场景:结合业务的缓存使用

1. 缓存 + 数据库(查询场景)

java

运行

java 复制代码
// 模拟Service层
@Service
@RequiredArgsConstructor
public class UserService {

    private final RedisUtil redisUtil;
    // 模拟数据库操作(实际项目注入Mapper/Repository)

    /**
     * 查询用户(先查缓存,缓存无则查库,再存缓存)
     */
    public User getUserById(Integer id) {
        String key = "user:info:" + id;
        // 1. 先查缓存
        User user = (User) redisUtil.get(key);
        if (user != null) {
            log.info("从缓存获取用户:{}", id);
            return user;
        }
        // 2. 缓存无,查数据库(模拟查询)
        log.info("从数据库获取用户:{}", id);
        user = new User(id, "数据库用户", 30);
        // 3. 存入缓存(设置过期时间,避免缓存永久有效)
        redisUtil.set(key, user, 300);
        return user;
    }
}

2. 缓存更新 / 删除(修改 / 删除场景)

java

运行

java 复制代码
/**
 * 更新用户(更新数据库后,删除缓存)
 */
public void updateUser(User user) {
    // 1. 更新数据库(模拟)
    log.info("更新数据库用户:{}", user);
    // 2. 删除缓存(缓存更新策略:先更库,再删缓存,避免缓存脏数据)
    String key = "user:info:" + user.getId();
    redisUtil.delete(key);
}

五、避坑指南

  1. 对象序列化问题 :存储 Java 对象时,实体类必须实现Serializable接口,否则会抛出序列化异常;
  2. 缓存过期时间:生产环境务必给缓存设置过期时间,避免 Redis 内存溢出;
  3. key 命名规范 :建议用业务名:模块名:id的格式(如user:info:1),方便管理和排查;
  4. 连接池配置 :必须引入commons-pool2依赖,否则连接池配置不生效,影响性能;
  5. 缓存穿透 / 击穿 / 雪崩
    • 穿透:缓存和数据库都无数据,可缓存空值;
    • 击穿:热点 key 过期瞬间大量请求打向数据库,可设置热点 key 永不过期;
    • 雪崩:大量 key 同时过期,可给过期时间加随机值。

总结

  1. SpringBoot 整合 Redis 核心依赖是spring-boot-starter-data-redis,配合连接池依赖可提升连接性能;
  2. 自定义RedisTemplate的序列化方式(String+JSON)是关键,能解决默认序列化乱码问题;
  3. 实际开发中建议封装 Redis 工具类,业务层遵循 "先查缓存,再查数据库,查库后更新缓存" 的核心逻辑,同时注意设置过期时间、规范 key 命名,避免缓存问题。

今天的学习重点是 Redis 的基础整合和实战用法,后续会深入讲解 Redis 缓存注解(@Cacheable等)、分布式锁等进阶知识点,一起把 Redis 用透~

相关推荐
小毅&Nora3 小时前
【后端】【Redis】① Redis8向量新特性:从零开始构建你的智能搜索系统
redis·向量
九皇叔叔3 小时前
application.yml 文件无 Spring 图标 + 无自动提示
java·spring boot·spring
heartbeat..4 小时前
Redis 常用命令全解析:基础、进阶与场景化实战
java·数据库·redis·缓存
让我上个超影吧4 小时前
天机学堂——多级缓存
java·spring boot·spring cloud
一只酸奶牛^_^5 小时前
解决LinuxDeploy部署mysql、redis数据库无法启动问题。
redis·mysql
小王不爱笑1326 小时前
@PropertySource&@ImportResource&@Bean
spring boot
optimistic_chen6 小时前
【Redis系列】分布式锁
linux·数据库·redis·分布式·缓存
Wyy_9527*6 小时前
行为型设计模式——状态模式
java·spring boot·后端
编程彩机6 小时前
互联网大厂Java面试:从分布式事务到微服务架构场景应用
spring boot·分布式事务·微服务架构·java面试·电商场景