SpringBoot整合缓存2-Redis

一、是什么:缓存的基本概念

缓存是一种存储技术,用于临时保存频繁访问的数据,以减少对数据库的直接访问,从而提升系统响应速度和降低数据库压力。在本案例中,我们使用 Redis 作为缓存中间件,结合 SpringBoot 和 MyBatis-Plus 实现对图书数据的缓存管理。

二、为什么:使用缓存的原因

  1. 提升性能:Redis 是内存数据库,读写速度远快于磁盘数据库(如 MySQL),减少数据库 IO 操作。
  2. 减轻数据库压力:高频访问的数据从缓存获取,降低数据库的查询负载。
  3. 改善用户体验:减少接口响应时间,提升系统流畅度。

三、怎么做:具体实现步骤

image-20251024185643182

1. 环境准备

  • JDK
  • SpringBoot 2.7.x
  • MySQL
  • Redis
  • MyBatis-Plus

数据库脚本

-- 创建数据库
CREATE DATABASE IF NOT EXISTS book_cache_demo DEFAULT CHARACTER SET utf8;

-- 使用数据库
USE book_cache_demo;

-- 创建图书表
CREATE TABLE `t_book` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '图书ID',
  `book_name` varchar(100) NOT NULL COMMENT '图书名称',
  `author` varchar(50) NOT NULL COMMENT '作者',
  `publish_time` date DEFAULT NULL COMMENT '出版时间',
  `price` decimal(10,2) NOT NULL COMMENT '价格',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='图书表';

2. 创建项目并添加依赖

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.7.4</version>
    <relativePath/> <!-- lookup parent from repository -->
  </parent>
  <groupId>com.yqd</groupId>
  <artifactId>RedisCache-Demo</artifactId>
  <version>1.0-SNAPSHOT</version>
  <packaging>jar</packaging>

  <name>RedisCache-Demo</name>
  <url>http://maven.apache.org</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

  <dependencies>
    <!-- Spring Web -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- 缓存抽象层 -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-cache</artifactId>
    </dependency>

    <!-- Redis -->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>

    <!-- MyBatis-Plus -->
    <dependency>
      <groupId>com.baomidou</groupId>
      <artifactId>mybatis-plus-boot-starter</artifactId>
      <version>3.5.3.1</version>
    </dependency>

    <!-- MySQL驱动 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <scope>runtime</scope>
    </dependency>

    <!-- Lombok -->
    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

3. 配置文件(application.yml)

spring:
  # 数据库配置
  datasource:
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://localhost:3306/book_cache_demo?useSSL=false&serverTimezone=UTC
    username: root  # 替换为你的MySQL用户名
    password: 123456  # 替换为你的MySQL密码

  # Redis配置
  redis:
    host: localhost  # Redis地址
    port: 6379       # Redis端口
    password:        # Redis密码(无密码则留空)
    timeout: 2000ms  # 连接超时时间

  # 缓存配置(Redis)
  cache:
    type: redis
    redis:
      time-to-live: 60000ms  # 缓存过期时间(60秒)
      key-prefix: "user:"    # 缓存key前缀(避免与其他缓存冲突)
      cache-null-values: false  # 不缓存null值

# MyBatis-Plus配置
mybatis-plus:
  mapper-locations: classpath:mapper/*.xml
  type-aliases-package: com.example.entity
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl  # 打印SQL,验证缓存是否生效

4. 核心代码实现

(1)实体类(Entity)

package com.yqd.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;  // 导入Serializable
import java.math.BigDecimal;
import java.util.Date;

@Data
@TableName("t_book")
public class Book implements Serializable {  // 实现Serializable接口
    private static final long serialVersionUID = 1L;  // 序列化版本号(建议添加)

    @TableId(type = IdType.AUTO)
    private Long id;
    private String bookName;
    private String author;
    private Date publishTime;
    private BigDecimal price;
}

(2)Mapper 接口(继承 MyBatis-Plus 的 BaseMapper)

package com.yqd.mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;

import com.yqd.entity.Book;
import org.apache.ibatis.annotations.Mapper;

@Mapper
public interface BookMapper extends BaseMapper<Book> {
    // 继承BaseMapper,无需手动编写CRUD方法
}

(3)Service 层(实现缓存逻辑)

package com.yqd.service;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.yqd.entity.Book;
import com.yqd.mapper.BookMapper;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

@Service
@CacheConfig(cacheNames = "book")  // 缓存名称,与前缀拼接为"book:xxx"
public class BookService extends ServiceImpl<BookMapper, Book> {

    /**
     * 查询图书:优先从Redis缓存获取,无则查库并写入缓存
     */
    @Cacheable(key = "#id")  // 缓存key:book:id
    public Book getById(Long id) {
        System.out.println("【数据库查询】图书ID=" + id);  // 验证是否走缓存
        return baseMapper.selectById(id);
    }

    /**
     * 新增图书:入库后同步写入缓存
     */
    @CachePut(key = "#result.id")  // 用新增图书的id作为缓存key
    public Book add(Book book) {
        baseMapper.insert(book);
        return book;
    }

    /**
     * 更新图书:更新数据库后,同步更新缓存
     */
    @CachePut(key = "#book.id")  // 缓存key:book:book.id
    public Book update(Book book) {
        baseMapper.updateById(book);
        return book;
    }

    /**
     * 删除图书:删除数据库后,清除对应缓存
     */
    @CacheEvict(key = "#id")  // 清除key为book:id的缓存
    public void delete(Long id) {
        baseMapper.deleteById(id);
    }

    /**
     * 清除所有图书缓存
     */
    @CacheEvict(allEntries = true)  // 清除所有key以book:开头的缓存
    public void clearAllCache() {
        System.out.println("【清除所有图书缓存】");
    }
}

(4)Controller 层(接口测试)

package com.yqd.controller;

import com.yqd.entity.Book;
import com.yqd.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/books")
public class BookController {

    @Autowired
    private BookService bookService;

    // 查询图书
    @GetMapping("/{id}")
    public Book get(@PathVariable Long id) {
        return bookService.getById(id);
    }

    // 新增图书
    @PostMapping
    public Book add(@RequestBody Book book) {
        return bookService.add(book);
    }

    // 更新图书
    @PutMapping
    public Book update(@RequestBody Book book) {
        return bookService.update(book);
    }

    // 删除图书
    @DeleteMapping("/{id}")
    public String delete(@PathVariable Long id) {
        bookService.delete(id);
        return "删除成功";
    }

    // 清除所有缓存
    @DeleteMapping("/clearCache")
    public String clearCache() {
        bookService.clearAllCache();
        return "所有图书缓存已清除";
    }
}

(5)启动类

package com.yqd;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;

@SpringBootApplication
@EnableCaching  // 开启缓存功能
@MapperScan("com.yqd.mapper")  // 扫描Mapper接口
public class RedisCacheDemo {
    public static void main(String[] args) {
        SpringApplication.run(RedisCacheDemo.class, args);
    }
}

(6)Redis配置类(可选)

package com.yqd.config;

import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
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.RedisSerializationContext;
import org.springframework.data.redis.serializer.RedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.time.Duration;

@EnableCaching
@Configuration
public class RedisCacheConfig extends CachingConfigurerSupport {
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // key序列化
        redisTemplate.setKeySerializer(redisSerializer);
        // value序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        // hashvalue序列化
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        return redisTemplate;
    }

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisSerializer<String> redisSerializer = new StringRedisSerializer();
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
                // 过期时间500秒
                .entryTtl(Duration.ofSeconds(500))
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(redisSerializer))
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(jackson2JsonRedisSerializer))
                .disableCachingNullValues();
        RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(redisCacheConfiguration)
                .build();
        return cacheManager;
    }
}

四、测试验证

  1. 初始化数据:向t_book表插入测试数据(如INSERT INTO t_book(book_name, author, publish_time, price) VALUES('SpringBoot实战', '张三', '2023-01-01', 59.90);)。
  2. 查询测试:
    • 首次访问GET http://localhost:8080/books/1,控制台打印 “从数据库获取数据并缓存”。
    • 再次访问同一接口,控制台打印 “从缓存获取数据”,说明缓存生效。
  3. 更新测试:
    • 调用PUT http://localhost:8080/books更新数据,控制台显示 “删除缓存”。
    • 再次查询,会重新从数据库获取并更新缓存。
  4. 删除测试:
    • 调用DELETE http://localhost:8080/books/1,缓存被删除,再次查询会返回空(数据库记录已删除)。

五、缓存注意事项

  1. 缓存一致性:更新 / 删除数据时需同步操作缓存(删除或更新),避免缓存与数据库数据不一致。
  2. 过期时间:设置缓存过期时间(如 30 分钟),防止缓存无限期存储导致内存溢出或数据过时。
  3. 缓存穿透:对不存在的 ID 查询,可缓存空值(短期),避免频繁访问数据库。
  4. 缓存雪崩:不同 key 设置随机过期时间,避免同时失效导致数据库压力骤增。
posted @ 2025-10-24 18:57  碧水云天4  阅读(21)  评论(0)    收藏  举报