缓存抽象
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 缓存抽象:
- 核心概念:Cache、CacheManager、缓存注解
- 缓存配置:SimpleCacheManager、RedisCacheManager
- @Cacheable:缓存方法结果
- @CachePut:更新缓存
- @CacheEvict:清除缓存
- @Caching:组合缓存操作
- @CacheConfig:类级别配置
- 最佳实践:过期时间、大对象、穿透和雪崩防护
Spring 的缓存抽象让缓存使用变得简单透明,是提升应用性能的重要工具。