Spring缓存(一):注解指南

Spring缓存(一):注解指南

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();

}

}通过合理使用这些缓存注解,你可以大幅提升应用性能,减少数据库压力。

缓存不是万能的,要根据业务特点选择合适的缓存策略,避免过度缓存导致的数据一致性问题。

相关推荐