Spring Boot 缓存机制与性能优化实践
在 Java 开发中,缓存与性能优化是提升系统响应速度和吞吐量的关键手段。通过合理引入缓存层,我们可以有效减少数据库的访问压力,改善用户体验。本文将深入探讨 Spring Boot 如何集成本地缓存与 Redis 分布式缓存,并通过实际代码演示具体的落地步骤。
缓存与性能优化概述
缓存本质上是一种临时数据存储区,旨在加速数据访问。常见的缓存方案包括内存缓存(如 ConcurrentHashMap、Guava Cache)、分布式缓存(如 Redis、Memcached)以及浏览器缓存。性能优化则是一个更广泛的概念,涵盖缓存策略、数据库查询调优、代码逻辑重构及服务器资源配置等多个维度。
在实际场景中,选择合适的缓存方案至关重要。对于单机应用,本地缓存足以应对;而对于高并发或集群环境,Redis 等分布式缓存则是必选项。
集成 Spring Cache 本地缓存
Spring Boot 提供了统一的缓存抽象(Spring Cache),让我们可以专注于业务逻辑而非底层存储细节。以下是基于本地内存缓存的完整实现流程。
1. 依赖配置
首先需要在 pom.xml 中添加缓存启动器依赖:
<dependencies>
<!-- 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>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
2. 启用缓存功能
在主启动类上添加 @EnableCaching 注解,开启 Spring 的缓存支持:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;
@SpringBootApplication
@EnableCaching
public class CacheApplication {
public static void main(String[] args) {
SpringApplication.run(CacheApplication.class, args);
}
}
3. 实体与 Repository 层
为了模拟真实场景,我们定义一个产品实体和 Repository 接口。注意这里使用了 Thread.sleep 来模拟数据库查询延迟。
public class Product {
private Long id;
private String productId;
private String productName;
private double price;
private int stock;
// 构造函数、Getter/Setter 省略,请确保格式规范
public Product() {}
public Product(Long id, String productId, String productName, double price, int stock) {
this.id = id;
this.productId = productId;
this.productName = productName;
this.price = price;
this.stock = stock;
}
// Getter and Setter methods...
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }
public String getProductId() { return productId; }
public void setProductId(String productId) { this.productId = productId; }
public String getProductName() { return productName; }
public void setProductName(String productName) { this.productName = productName; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
public int getStock() { return stock; }
public void setStock(int stock) { this.stock = stock; }
@Override
public String toString() {
return "Product{" +
"id=" + id +
", productId='" + productId + '\'' +
", productName='" + productName + '\'' +
", price=" + price +
", stock=" + stock +
'}';
}
}
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
@Repository
public class ProductRepository {
private List<Product> products = new ArrayList<>();
public ProductRepository() {
products.add(new Product(1L, "P001", "手机", 1000.0, 100));
products.add(new Product(2L, "P002", "电脑", 5000.0, 50));
products.add(new Product(3L, "P003", "电视", 3000.0, 80));
products.add(new Product(4L, "P004", "手表", 500.0, 200));
products.add(new Product(5L, "P005", "耳机", 300.0, 150));
}
public List<Product> getAllProducts() {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
return products;
}
public Product getProductById(Long id) {
try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); }
return products.stream().filter(p -> p.getId().equals(id)).findFirst().orElse(null);
}
public void addProduct(Product product) {
product.setId((long) (products.size() + 1));
products.add(product);
}
public void updateProduct(Product product) {
Product existing = getProductById(product.getId());
if (existing != null) {
existing.setProductId(product.getProductId());
existing.setProductName(product.getProductName());
existing.setPrice(product.getPrice());
existing.setStock(product.getStock());
}
}
public void deleteProduct(Long id) {
products.removeIf(p -> p.getId().equals(id));
}
}
4. Service 层缓存注解
这是核心部分。我们使用 @Cacheable 标记需要缓存的方法,@CachePut 用于更新后刷新缓存,@CacheEvict 用于删除时清除缓存。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.*;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class ProductService {
@Autowired
private ProductRepository productRepository;
// 缓存所有产品列表,key 为 "products"
@Cacheable(value = "products")
public List<Product> getAllProducts() {
System.out.println("从数据库查询所有产品信息");
return productRepository.getAllProducts();
}
// 缓存单个产品,key 为 "product:id"
@Cacheable(value = "product", key = "#id")
public Product getProductById(Long id) {
System.out.println("从数据库查询产品信息:" + id);
return productRepository.getProductById(id);
}
// 更新操作,先执行方法再写入缓存
@CachePut(value = "product", key = "#product.id")
public Product addProduct(Product product) {
System.out.println("添加产品信息:" + product);
productRepository.addProduct(product);
return product;
}
@CachePut(value = "product", key = "#product.id")
public Product updateProduct(Product product) {
System.out.println("更新产品信息:" + product);
productRepository.updateProduct(product);
return product;
}
// 删除操作,清除对应缓存
@CacheEvict(value = "product", key = "#id")
public void deleteProduct(Long id) {
System.out.println("删除产品信息:" + id);
productRepository.deleteProduct(id);
}
}
5. Controller 层与应用测试
Controller 层保持标准 RESTful 风格,无需修改缓存逻辑。启动应用后,观察控制台输出即可验证缓存效果:第一次请求会打印'从数据库查询',第二次请求则直接返回结果,不再打印日志,说明命中了缓存。
集成 Redis 分布式缓存
当应用部署在多台服务器上时,本地缓存无法共享数据,此时需要切换到 Redis。主要区别在于依赖引入和配置文件调整。
1. 依赖与配置
在 pom.xml 中增加 Redis 依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
在 application.properties 中配置 Redis 连接信息:
server.port=8080
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
2. 业务逻辑复用
Service 层的代码与本地缓存版本完全一致。Spring Cache 默认会根据配置自动切换后端存储。只要启用了 Redis 客户端并配置正确,@Cacheable 等注解会自动将数据序列化存入 Redis。
3. 测试验证
运行测试类,对比两次请求的响应时间和控制台日志。你会发现,即使重启应用,Redis 中的数据依然存在,实现了真正的分布式缓存效果。
总结
通过本文的实践,我们掌握了 Spring Boot 缓存集成的核心流程。无论是简单的本地内存缓存还是生产级的 Redis 分布式缓存,Spring Cache 都提供了统一的编程模型。关键在于根据业务场景选择合适的缓存策略,合理使用注解控制缓存行为,从而在保证数据一致性的前提下,最大化系统的性能表现。

