什么是SpringBoot缓存

SpringBoot提供了强大的缓存支持,通过注解驱动的方式,让开发者能够轻松地实现缓存功能,从而提升应用性能。本文将详细介绍SpringBoot中的缓存注解使用方法和最佳实践。

缓存注解概述

SpringBoot提供了以下几个主要的缓存注解:

@Cacheable:将方法的返回结果缓存起来
@CachePut:更新缓存,不影响方法的执行
@CacheEvict:清除缓存
@Caching:组合多个缓存操作
@CacheConfig:类级别的缓存配置

开启缓存支持

在SpringBoot应用中启用缓存功能非常简单,只需要添加相关依赖并开启缓存支持:

1
2
3
4
5
<!-- pom.xml -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>

在启动类上添加@EnableCaching注解:

1
2
3
4
5
6
7
@SpringBootApplication
@EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

@Cacheable注解详解

@Cacheable是最常用的缓存注解,用于将方法的返回结果存储到缓存中。

基本用法

1
2
3
4
5
6
7
8
@Service
public class UserService {
@Cacheable(value = "users", key = "#id")
public User getUserById(Long id) {
// 模拟从数据库获取用户信息
return userRepository.findById(id);
}
}

重要属性

value/cacheNames:指定缓存的名称
key:缓存的键,支持SpEL表达式
condition:缓存的条件
unless:不缓存的条件

高级用法

1
2
3
4
5
6
7
8
9
@Cacheable(
value = "users",
key = "#id",
condition = "#id > 0",
unless = "#result == null"
)
public User getUserById(Long id) {
return userRepository.findById(id);
}

@CachePut注解使用

@CachePut注解用于更新缓存,它总是会执行方法并将结果更新到缓存中。

1
2
3
4
5
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
// 更新用户信息
return userRepository.save(user);
}

@CacheEvict注解使用

@CacheEvict用于删除缓存中的数据。

删除单个缓存

1
2
3
4
@CacheEvict(value = "users", key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}

清空整个缓存

1
2
3
4
@CacheEvict(value = "users", allEntries = true)
public void clearUserCache() {
// 清空users缓存
}

@Caching组合注解

当需要在一个方法上组合多个缓存操作时,可以使用@Caching注解。

1
2
3
4
5
6
7
8
9
10
11
12
@Caching(
cacheable = {
@Cacheable(value = "users", key = "#username")
},
put = {
@CachePut(value = "users", key = "#result.id"),
@CachePut(value = "users", key = "#result.email")
}
)
public User findByUsername(String username) {
return userRepository.findByUsername(username);
}

@CacheConfig类级别配置

使用@CacheConfig可以在类级别设置一些共同的缓存配置。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Service
@CacheConfig(cacheNames = "users")
public class UserService {

@Cacheable(key = "#id")
public User getUser(Long id) {
return userRepository.findById(id);
}

@CachePut(key = "#user.id")
public User updateUser(User user) {
return userRepository.save(user);
}

@CacheEvict(key = "#id")
public void deleteUser(Long id) {
userRepository.deleteById(id);
}
}

缓存配置

配置缓存管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
@Configuration
public class CacheConfig {

@Bean
public CacheManager cacheManager() {
SimpleCacheManager cacheManager = new SimpleCacheManager();
cacheManager.setCaches(Arrays.asList(
new ConcurrentMapCache("users"),
new ConcurrentMapCache("roles")
));
return cacheManager;
}
}

使用Caffeine作为本地缓存

Caffeine是一个高性能的Java本地缓存库,可以提供比ConcurrentMapCache更好的性能和更多的功能特性。

1
2
3
4
5
<!-- pom.xml -->
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>

在配置文件中启用Caffeine缓存:

1
2
3
4
5
6
# application.yml
spring:
cache:
type: caffeine
caffeine:
spec: maximumSize=500,expireAfterWrite=30s

自定义Caffeine缓存配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@Configuration
public class CaffeineConfig {
@Bean
public CacheManager cacheManager() {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();

// 配置Caffeine缓存
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
.maximumSize(1000) // 最大缓存对象数
.expireAfterWrite(Duration.ofMinutes(15)) // 写入后过期时间
.expireAfterAccess(Duration.ofMinutes(5)) // 访问后过期时间
.recordStats(); // 开启统计

cacheManager.setCaffeine(caffeine);
// 设置缓存名称
cacheManager.setCacheNames(Arrays.asList("users", "roles"));
return cacheManager;
}

// 监控Caffeine缓存状态
@Bean
public CacheMetricsCollector cacheMetricsCollector(CacheManager cacheManager) {
CacheMetricsCollector collector = new CacheMetricsCollector();
if (cacheManager instanceof CaffeineCacheManager) {
CaffeineCacheManager caffeineCacheManager = (CaffeineCacheManager) cacheManager;
caffeineCacheManager.getCacheNames().forEach(name -> {
Cache cache = caffeineCacheManager.getCache(name);
if (cache instanceof CaffeineCache) {
collector.addCache(name, ((CaffeineCache) cache).getNativeCache());
}
});
}
return collector;
}
}

使用Redis作为缓存

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
1
2
3
4
5
6
7
# application.yml
spring:
cache:
type: redis
redis:
host: localhost
port: 6379

最佳实践

合理设置缓存过期时间

1
2
3
4
5
6
7
8
9
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10));

return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}

使用合适的键生成策略

1
2
3
4
@Cacheable(value = "users", key = "T(java.lang.String).format('user:%d', #id)")
public User getUser(Long id) {
return userRepository.findById(id);
}

避免缓存穿透

1
2
3
4
5
6
7
8
@Cacheable(
value = "users",
key = "#id",
unless = "#result == null"
)
public User getUser(Long id) {
return userRepository.findById(id);
}

合理使用condition和unless

1
2
3
4
5
6
7
8
9
@Cacheable(
value = "users",
key = "#id",
condition = "#id != null",
unless = "#result == null || #result.status == 'INACTIVE'"
)
public User getUser(Long id) {
return userRepository.findById(id);
}

性能优化建议

选择合适的缓存实现
对于小型应用,可以使用ConcurrentMapCache
对于分布式应用,推荐使用Redis

设置合理的缓存大小
避免缓存过多数据导致内存压力
根据实际业务需求设置缓存容量

使用缓存预热
系统启动时预先加载热点数据
避免系统初期的性能问题

监控缓存性能
监控缓存命中率
监控缓存容量使用情况

常见问题与解决方案

缓存穿透问题

问题:频繁查询不存在的数据导致请求直接打到数据库

解决方案:

1
2
3
4
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public Optional<User> getUser(Long id) {
return Optional.ofNullable(userRepository.findById(id));
}

缓存击穿问题

问题:热点数据过期导致大量请求直接访问数据库

解决方案:使用互斥锁或设置永不过期

1
2
3
4
@Cacheable(value = "users", key = "#id", sync = true)
public User getUser(Long id) {
return userRepository.findById(id);
}

缓存雪崩问题

问题:大量缓存同时失效

解决方案:设置随机过期时间

1
2
3
4
5
6
7
8
9
10
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
Random random = new Random();
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10 + random.nextInt(5)));

return RedisCacheManager.builder(factory)
.cacheDefaults(config)
.build();
}

总结

SpringBoot的缓存注解提供了一种简单而强大的方式来实现应用程序的缓存功能。通过合理使用这些注解,可以显著提升应用性能。关键点包括:

选择合适的缓存注解
正确配置缓存参数
处理好缓存更新和失效
注意性能优化和问题处理