跳到主要内容

缓存抽象

Spring 提供了统一的缓存抽象,支持多种缓存实现。本章介绍 Spring 缓存的使用方法和最佳实践。

缓存概述

缓存是提高应用性能的重要手段。Spring 从 3.1 开始提供了统一的缓存抽象,让开发者可以透明地使用各种缓存技术。

Spring 缓存抽象的核心概念:

概念说明
Cache缓存接口,定义缓存的基本操作
CacheManager缓存管理器,管理多个 Cache
@Cacheable缓存方法结果
@CachePut更新缓存
@CacheEvict清除缓存
@Caching组合多个缓存操作
@CacheConfig类级别缓存配置

启用缓存

Java 配置

@Configuration
@EnableCaching
public class CacheConfig {

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

使用 Redis 作为缓存

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
@Configuration
@EnableCaching
public class RedisCacheConfig {

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30))
.disableCachingNullValues()
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));

return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.withInitialCacheConfigurations(getCacheConfigurations())
.build();
}

private Map<String, RedisCacheConfiguration> getCacheConfigurations() {
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();
configMap.put("users", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)));
configMap.put("products", RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)));
return configMap;
}
}

@Cacheable 注解

@Cacheable 用于缓存方法返回值。下次调用相同参数的方法时,直接从缓存获取结果,不执行方法体。

基本用法

@Service
public class UserService {

@Autowired
private UserDao userDao;

@Cacheable("users")
public User findById(Long id) {
System.out.println("从数据库查询用户: " + id);
return userDao.findById(id);
}

@Cacheable(value = "users", key = "#email")
public User findByEmail(String email) {
System.out.println("从数据库查询用户: " + email);
return userDao.findByEmail(email);
}
}

自定义缓存 Key

@Service
public class ProductService {

@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
return productDao.findById(id);
}

@Cacheable(value = "products", key = "#category + ':' + #id")
public Product findByCategoryAndId(String category, Long id) {
return productDao.findByCategoryAndId(category, id);
}

@Cacheable(value = "products", key = "#product.id")
public Product save(Product product) {
return productDao.save(product);
}

@Cacheable(value = "products", keyGenerator = "customKeyGenerator")
public List<Product> findByCriteria(ProductCriteria criteria) {
return productDao.findByCriteria(criteria);
}
}

@Component("customKeyGenerator")
public class CustomKeyGenerator implements KeyGenerator {

@Override
public Object generate(Object target, Method method, Object... params) {
StringBuilder sb = new StringBuilder();
sb.append(target.getClass().getSimpleName()).append(":");
sb.append(method.getName()).append(":");
for (Object param : params) {
sb.append(param.toString()).append(":");
}
return sb.toString();
}
}

条件缓存

@Service
public class UserService {

@Cacheable(value = "users", condition = "#id > 0")
public User findById(Long id) {
return userDao.findById(id);
}

@Cacheable(value = "users", unless = "#result == null")
public User findByEmail(String email) {
return userDao.findByEmail(email);
}

@Cacheable(value = "users", condition = "#id > 0", unless = "#result.status == T(com.example.UserStatus).INACTIVE")
public User findActiveUser(Long id) {
return userDao.findById(id);
}
}

@CachePut 注解

@CachePut 用于更新缓存,每次都会执行方法体,并将结果存入缓存。

@Service
public class UserService {

@CachePut(value = "users", key = "#user.id")
public User update(User user) {
return userDao.save(user);
}

@CachePut(value = "users", key = "#result.id")
public User create(User user) {
return userDao.save(user);
}
}

@CacheEvict 注解

@CacheEvict 用于清除缓存。

@Service
public class UserService {

@CacheEvict(value = "users", key = "#id")
public void delete(Long id) {
userDao.deleteById(id);
}

@CacheEvict(value = "users", allEntries = true)
public void deleteAll() {
userDao.deleteAll();
}

@CacheEvict(value = "users", allEntries = true, beforeInvocation = true)
public void refreshAll() {
}
}

@Caching 注解

@Caching 用于组合多个缓存操作。

@Service
public class UserService {

@Caching(
put = {
@CachePut(value = "users", key = "#user.id"),
@CachePut(value = "users", key = "#user.email")
}
)
public User update(User user) {
return userDao.save(user);
}

@Caching(
evict = {
@CacheEvict(value = "users", key = "#id"),
@CacheEvict(value = "userOrders", key = "#id")
}
)
public void delete(Long id) {
userDao.deleteById(id);
}

@Caching(
cacheable = @Cacheable("users"),
evict = @CacheEvict(value = "userStats", key = "#id")
)
public User findByIdWithStats(Long id) {
return userDao.findById(id);
}
}

@CacheConfig 注解

@CacheConfig 用于类级别的缓存配置,避免重复配置。

@Service
@CacheConfig(cacheNames = "users")
public class UserService {

@Cacheable
public User findById(Long id) {
return userDao.findById(id);
}

@Cacheable(key = "#email")
public User findByEmail(String email) {
return userDao.findByEmail(email);
}

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

实际应用示例

商品缓存

@Service
@CacheConfig(cacheNames = "products")
public class ProductService {

@Autowired
private ProductDao productDao;

@Cacheable(key = "#id")
public Product findById(Long id) {
return productDao.findById(id);
}

@Cacheable(key = "'category:' + #category")
public List<Product> findByCategory(String category) {
return productDao.findByCategory(category);
}

@CachePut(key = "#product.id")
public Product save(Product product) {
return productDao.save(product);
}

@Caching(
evict = {
@CacheEvict(key = "#id"),
@CacheEvict(key = "'category:' + #result.category", beforeInvocation = false)
}
)
public Product updateCategory(Long id, String category) {
Product product = productDao.findById(id);
product.setCategory(category);
return productDao.save(product);
}

@CacheEvict(allEntries = true)
public void refreshCache() {
}
}

用户权限缓存

@Service
public class PermissionService {

@Autowired
private PermissionDao permissionDao;

@Cacheable(value = "permissions", key = "#userId")
public Set<String> getUserPermissions(Long userId) {
return permissionDao.findPermissionsByUserId(userId);
}

@CacheEvict(value = "permissions", key = "#userId")
public void refreshUserPermissions(Long userId) {
}

@CacheEvict(value = "permissions", allEntries = true)
public void refreshAllPermissions() {
}
}

缓存最佳实践

1. 合理设置过期时间

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
Map<String, RedisCacheConfiguration> configMap = new HashMap<>();

configMap.put("users", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1)));

configMap.put("products", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(10)));

configMap.put("permissions", RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)));

return RedisCacheManager.builder(connectionFactory)
.withInitialCacheConfigurations(configMap)
.build();
}

2. 避免缓存大对象

@Cacheable(value = "users", key = "#id", unless = "#result.orders.size() > 100")
public User findById(Long id) {
return userDao.findById(id);
}

3. 缓存穿透防护

@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User findById(Long id) {
return userDao.findById(id);
}

4. 缓存雪崩防护

设置不同的过期时间,避免大量缓存同时失效:

private Duration getRandomTtl(Duration base) {
long seconds = base.getSeconds();
long randomSeconds = seconds + ThreadLocalRandom.current().nextLong(0, seconds / 10);
return Duration.ofSeconds(randomSeconds);
}

小结

本章介绍了 Spring 缓存抽象:

  1. 核心概念:Cache、CacheManager、缓存注解
  2. 缓存配置:SimpleCacheManager、RedisCacheManager
  3. @Cacheable:缓存方法结果
  4. @CachePut:更新缓存
  5. @CacheEvict:清除缓存
  6. @Caching:组合缓存操作
  7. @CacheConfig:类级别配置
  8. 最佳实践:过期时间、大对象、穿透和雪崩防护

Spring 的缓存抽象让缓存使用变得简单透明,是提升应用性能的重要工具。