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 "缓存已清空";
}
}
七、验证缓存是否生效
启动项目,按下面步骤测试,观察控制台日志:
- 第一次查询:浏览器访问
http://localhost:8080/api/user/1控制台打印执行了【数据库查询】,说明第一次走了 “数据库” - 第二次刷新:同样的地址再点一次
控制台没有任何日志,直接返回了结果,说明缓存生效 ✅
- 更新数据:调用更新接口
http://localhost:8080/api/user/1?name=张老三控制台打印更新日志,同时缓存被刷新 - 再查询:查询 id=1,返回最新名称,且没有查库日志
八、三大核心注解对比
表格
| 注解 | 行为 | 适用场景 |
|---|---|---|
@Cacheable |
有缓存就返回,没缓存就执行方法并存入 | 读多写少的查询 |
@CachePut |
每次都执行方法,用结果更新缓存 | 更新数据,同步缓存 |
@CacheEvict |
方法执行后删除指定缓存 | 删除数据、失效缓存 |
九、最佳实践总结
- 不要用默认缓存:Spring Boot 默认的 ConcurrentHashMap 无过期、无容量限制,生产必须用 Caffeine / Redis
- 必须配置 TTL:即使做了主动更新,也要加过期时间兜底,防止脏数据永久存在
- 限制缓存容量:设置
maximumSize,防止缓存无限增长撑爆内存 - 缓存加在 Service 层:不要加在 Controller,和事务同理,AOP 代理才会生效
- 更新 / 删除必清缓存:数据变更后同步处理缓存,保证一致性
- 同类自调用失效:本类方法互相调用,缓存注解不生效(和事务坑点一样)
- 不缓存写频繁、强一致的数据:缓存适合读多写少、允许短暂不一致的场景

浙公网安备 33010602011771号