Spring boot Cache ----18

 

全新独立项目,无需数据库、零额外配置,复制即可启动运行,完整覆盖课程 187~193 集所有核心知识点,同时遵循生产级最佳实践(Caffeine 缓存、TTL 过期、容量限制、缓存同步)。

一、项目结构(极简 4 个类)

plaintext
 
 
com.example.cache
├── SpringCacheDemoApplication.java    # 启动类
├── config
│   └── CacheConfig.java               # Caffeine 缓存配置
├── service
│   └── UserService.java               # 缓存核心逻辑
└── controller
    └── UserController.java            # 测试接口
 

二、第一步:pom.xml 依赖

只需要 3 个核心依赖,Spring Boot 3.x 版本。
xml
 
 
<?xml version="1.0" encoding="UTF-8"?>
<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 https://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>3.2.0</version>
        <relativePath/>
    </parent>

    <groupId>com.example</groupId>
    <artifactId>spring-cache-demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>17</java.version>
    </properties>

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

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

        <!-- Caffeine 高性能本地缓存(生产推荐,替代默认内存缓存) -->
        <dependency>
            <groupId>com.github.ben-manes.caffeine</groupId>
            <artifactId>caffeine</artifactId>
        </dependency>

        <!-- Lombok 简化代码,可选 -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>
 

三、第二步:启动类 + 开启缓存

java
 
运行
 
 
package com.example.cache;

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

@SpringBootApplication
@EnableCaching // 一行开启注解缓存,必须加
public class SpringCacheDemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringCacheDemoApplication.class, args);
    }
}
 

四、第三步:生产级缓存配置(对应 190、191 集)

用 Caffeine 替代默认缓存,配置 TTL 过期时间、最大容量,解决脏数据、内存溢出问题。
java
 
运行
 
 
package com.example.cache.config;

import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.cache.CacheManager;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.concurrent.TimeUnit;

@Configuration
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        CaffeineCacheManager manager = new CaffeineCacheManager();
        manager.setCaffeine(Caffeine.newBuilder()
                // 写入后 10 分钟过期(TTL):兜底防脏数据
                .expireAfterWrite(10, TimeUnit.MINUTES)
                // 最多缓存 1000 条:防止内存溢出,超出自动淘汰
                .maximumSize(1000)
                // 开启缓存统计:可查看命中率
                .recordStats()
        );
        return manager;
    }
}
 

五、第四步:缓存核心业务层(对应 187~189 集)

三大核心注解全覆盖,用 HashMap 模拟数据库,无需额外安装 MySQL,启动就能测试。
java
 
运行
 
 
package com.example.cache.service;

import lombok.extern.slf4j.Slf4j;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;

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

@Slf4j
@Service
public class UserService {

    // 用 Map 模拟数据库
    private final Map<Long, String> userDB = new HashMap<>();

    public UserService() {
        userDB.put(1L, "张三");
        userDB.put(2L, "李四");
        userDB.put(3L, "王五");
    }

    /**
     * 1. @Cacheable 查询缓存(对应 188 集)
     * 缓存名 = "user",key = 方法参数 id
     * 第一次执行方法(查库),结果存入缓存;后续同 id 直接返回缓存,不执行方法
     */
    @Cacheable(value = "user", key = "#userId")
    public String getUserName(Long userId) {
        log.info("⚠️  执行了【数据库查询】,userId = {}", userId);
        return userDB.getOrDefault(userId, "用户不存在");
    }

    /**
     * 2. @CachePut 更新缓存(对应 189 集)
     * 每次都会执行方法,执行成功后用最新结果更新缓存
     * 保证数据库和缓存数据一致
     */
    @CachePut(value = "user", key = "#userId")
    public String updateUserName(Long userId, String newName) {
        log.info("⚠️  执行了【数据库更新】,userId = {}, 新名称 = {}", userId, newName);
        userDB.put(userId, newName);
        return newName;
    }

    /**
     * 3. @CacheEvict 删除缓存(对应 189 集)
     * 方法执行成功后,删除指定 key 的缓存
     */
    @CacheEvict(value = "user", key = "#userId")
    public void deleteUser(Long userId) {
        log.info("⚠️  执行了【数据库删除】,userId = {}", userId);
        userDB.remove(userId);
    }

    /**
     * 清空 user 缓存下所有数据
     * allEntries = true:清空整个缓存分区
     */
    @CacheEvict(value = "user", allEntries = true)
    public void clearAllUserCache() {
        log.info("🗑️  清空了所有用户缓存");
    }
}
 

六、第五步:测试接口

java
 
运行
 
 
package com.example.cache.controller;

import com.example.cache.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api/user")
@RequiredArgsConstructor
public class UserController {

    private final UserService userService;

    // 查询用户:多次调用,观察控制台是否打印 SQL 日志
    @GetMapping("/{id}")
    public String getUser(@PathVariable Long id) {
        return userService.getUserName(id);
    }

    // 更新用户:更新后缓存同步刷新
    @PutMapping("/{id}")
    public String updateUser(@PathVariable Long id, @RequestParam String name) {
        return userService.updateUserName(id, name);
    }

    // 删除用户:删除后缓存同步清除
    @DeleteMapping("/{id}")
    public String deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return "删除成功";
    }

    // 清空所有缓存
    @DeleteMapping("/cache")
    public String clearCache() {
        userService.clearAllUserCache();
        return "缓存已清空";
    }
}
 

七、验证缓存是否生效

启动项目,按下面步骤测试,观察控制台日志:
  1. 第一次查询:浏览器访问 http://localhost:8080/api/user/1
     
    控制台打印 执行了【数据库查询】,说明第一次走了 “数据库”
  2. 第二次刷新:同样的地址再点一次
     
    控制台没有任何日志,直接返回了结果,说明缓存生效 ✅
  3. 更新数据:调用更新接口 http://localhost:8080/api/user/1?name=张老三
     
    控制台打印更新日志,同时缓存被刷新
  4. 再查询:查询 id=1,返回最新名称,且没有查库日志

八、三大核心注解对比

表格
 
注解行为适用场景
@Cacheable 有缓存就返回,没缓存就执行方法并存入 读多写少的查询
@CachePut 每次都执行方法,用结果更新缓存 更新数据,同步缓存
@CacheEvict 方法执行后删除指定缓存 删除数据、失效缓存

九、最佳实践总结

  1. 不要用默认缓存:Spring Boot 默认的 ConcurrentHashMap 无过期、无容量限制,生产必须用 Caffeine / Redis
  2. 必须配置 TTL:即使做了主动更新,也要加过期时间兜底,防止脏数据永久存在
  3. 限制缓存容量:设置 maximumSize,防止缓存无限增长撑爆内存
  4. 缓存加在 Service 层:不要加在 Controller,和事务同理,AOP 代理才会生效
  5. 更新 / 删除必清缓存:数据变更后同步处理缓存,保证一致性
  6. 同类自调用失效:本类方法互相调用,缓存注解不生效(和事务坑点一样)
  7. 不缓存写频繁、强一致的数据:缓存适合读多写少、允许短暂不一致的场景
posted @ 2026-08-01 02:23  漫漫长路</>  阅读(5)  评论(0)    收藏  举报