1. @Cacheable注解详解1.1 基本使用方式@Cacheable是Spring缓存体系中最常用的注解,它的工作原理很简单:方法第一次执行时会将结果存入缓存,后续相同参数的调用直接从缓存返回数据。
这种机制特别适合那些计算成本高或者数据变化不频繁的场景。
代码语言:java复制import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Cacheable(cacheNames = "userCache")
public User getUserById(Long id) {
// 模拟数据库查询操作
System.out.println("从数据库查询用户: " + id);
return userRepository.findById(id).orElse(null);
}
}上面这个例子中,当我们第一次调用getUserById(1L)时,会打印日志并执行数据库查询。
但第二次调用相同参数时,方法体不会执行,直接返回缓存中的User对象。
1.2 核心参数配置cacheNames参数
这是必填参数,用来指定缓存的存储区域。你可以把它理解为缓存的"房间号",不同的业务数据可以存放在不同的房间里。
代码语言:java复制@Cacheable(cacheNames = {"userCache", "memberCache"})
public User getUser(Long id) {
return userRepository.findById(id).orElse(null);
}key参数
用来自定义缓存的键值,支持SpEL表达式。如果不指定,Spring会根据方法参数自动生成。
代码语言:java复制@Cacheable(cacheNames = "userCache", key = "#id")
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
@Cacheable(cacheNames = "userCache", key = "#user.id + '_' + #user.name")
public User getUserByInfo(User user) {
return userRepository.findByIdAndName(user.getId(), user.getName());
}condition参数
这个参数让你可以控制什么时候才启用缓存,只有当条件为true时才会缓存结果。
代码语言:java复制@Cacheable(cacheNames = "userCache", condition = "#id > 0")
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}2. @CachePut注解应用2.1 更新缓存机制@CachePut和@Cacheable最大的区别是:它每次都会执行方法体,然后将结果更新到缓存中。
这个特性让它特别适合用在数据更新的场景。
代码语言:java复制@Service
public class UserService {
@CachePut(cacheNames = "userCache", key = "#user.id")
public User updateUser(User user) {
System.out.println("更新用户信息: " + user.getId());
User savedUser = userRepository.save(user);
return savedUser;
}
}2.2 实际业务场景假设我们有一个商品管理系统,当商品信息发生变化时,需要同时更新数据库和缓存。
代码语言:java复制@Service
public class ProductService {
@Cacheable(cacheNames = "productCache", key = "#id")
public Product getProduct(Long id) {
return productRepository.findById(id).orElse(null);
}
@CachePut(cacheNames = "productCache", key = "#product.id")
public Product updateProduct(Product product) {
// 更新商品价格
product.setUpdateTime(LocalDateTime.now());
return productRepository.save(product);
}
}这样设计的好处是,当商品信息更新后,缓存中的数据也会立即更新,保证了数据的一致性。
3. @CacheEvict注解详解3.1 缓存清理策略@CacheEvict专门用来清理缓存,当数据被删除或者失效时,对应的缓存也应该被清除。
代码语言:java复制@Service
public class UserService {
@CacheEvict(cacheNames = "userCache", key = "#id")
public void deleteUser(Long id) {
System.out.println("删除用户: " + id);
userRepository.deleteById(id);
}
}3.2 批量清理操作有时候我们需要清空整个缓存区域,比如系统维护或者数据大批量更新的时候。
代码语言:java复制@Service
public class UserService {
@CacheEvict(cacheNames = "userCache", allEntries = true)
public void clearAllUsers() {
System.out.println("清空所有用户缓存");
// 执行批量数据清理逻辑
}
@CacheEvict(cacheNames = "userCache", key = "#id", beforeInvocation = true)
public void deleteUserWithRisk(Long id) {
// 这个方法可能会抛异常,但我们希望无论如何都清除缓存
if (id == null) {
throw new IllegalArgumentException("用户ID不能为空");
}
userRepository.deleteById(id);
}
}beforeInvocation参数很有用,当设置为true时,即使方法执行失败,缓存也会被清除。
4. @Caching组合注解4.1 复杂缓存操作在实际项目中,有时候一个方法需要同时进行多种缓存操作。@Caching注解就是为了解决这个问题。
代码语言:java复制@Service
public class OrderService {
@Caching(
cacheable = {
@Cacheable(cacheNames = "orderCache", key = "#orderId")
},
put = {
@CachePut(cacheNames = "userOrderCache", key = "#result.userId + '_latest'")
},
evict = {
@CacheEvict(cacheNames = "orderStatCache", allEntries = true)
}
)
public Order processOrder(Long orderId) {
Order order = orderRepository.findById(orderId).orElse(null);
if (order != null) {
order.setStatus("PROCESSED");
order.setProcessTime(LocalDateTime.now());
orderRepository.save(order);
}
return order;
}
}这个例子展示了一个订单处理方法,它同时做了三件事:
将订单信息缓存到orderCache中将用户最新订单信息更新到userOrderCache中 清空订单统计缓存,因为统计数据已经发生变化4.2 电商系统实战案例假设我们在开发一个电商系统的商品评价功能,当用户提交评价后需要更新多个缓存。
代码语言:java复制@Service
public class ReviewService {
@Caching(
put = {
@CachePut(cacheNames = "reviewCache", key = "#review.id"),
@CachePut(cacheNames = "productReviewCache", key = "#review.productId + '_latest'")
},
evict = {
@CacheEvict(cacheNames = "productStatCache", key = "#review.productId"),
@CacheEvict(cacheNames = "userReviewCountCache", key = "#review.userId")
}
)
public Review submitReview(Review review) {
// 保存评价信息
Review savedReview = reviewRepository.save(review);
// 更新商品评分
updateProductRating(review.getProductId());
return savedReview;
}
private void updateProductRating(Long productId) {
// 重新计算商品平均评分
Double avgRating = reviewRepository.calculateAvgRating(productId);
productService.updateRating(productId, avgRating);
}
}5. 缓存实现原理5.1 Spring缓存抽象层Spring的缓存功能基于AOP实现,通过代理模式在方法调用前后插入缓存逻辑。
当你在方法上添加缓存注解时,Spring会创建一个代理对象来处理缓存操作。
代码语言:java复制@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
// 使用内存缓存
return new ConcurrentMapCacheManager("userCache", "productCache", "orderCache");
}
}5.2 多种缓存实现Spring支持多种缓存实现,你可以根据项目需求选择合适的方案。
代码语言:java复制// Redis缓存配置
@Configuration
@EnableCaching
public class RedisCacheConfig {
@Bean
public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(30)) // 设置过期时间
.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));
return RedisCacheManager.builder(connectionFactory)
.cacheDefaults(config)
.build();
}
}通过合理使用这些缓存注解,你可以大幅提升应用性能,减少数据库压力。
缓存不是万能的,要根据业务特点选择合适的缓存策略,避免过度缓存导致的数据一致性问题。