当前位置: 首页 > news >正文

Java 中使用 Redis 注解版缓存——补充

在现代应用开发中,缓存是提升系统性能的重要手段。Redis 作为高性能的内存数据库,在缓存领域应用广泛。Spring 框架提供了强大的缓存抽象,结合 Redis 可以实现简洁高效的缓存方案。本文将深入介绍如何在 Java 项目中使用注解方式开启 Redis 缓存。

一、Redis 缓存的优势

在开始之前,我们先了解为什么选择 Redis 作为缓存:

  1. 高性能:基于内存操作,读写速度极快
  2. 数据结构丰富:支持 String、Hash、List、Set、ZSet 等多种数据结构
  3. 持久化:支持 RDB 和 AOF 两种持久化方式,避免数据丢失
  4. 分布式支持:天然支持分布式环境,适合微服务架构
  5. 原子操作:提供丰富的原子操作命令
  6. 过期策略:支持键的过期时间设置
  7. 发布订阅:支持消息队列功能

二、Spring 缓存抽象

Spring 提供了一套缓存抽象,允许我们使用不同的缓存提供者(如 Redis、EhCache、Caffeine 等)而不需要修改业务逻辑。核心注解包括:

  • @EnableCaching - 启用缓存功能
  • @Cacheable - 触发缓存读取
  • @CachePut - 触发缓存更新
  • @CacheEvict - 触发缓存删除
  • @Caching - 组合多个缓存操作
  • @CacheConfig - 类级别的缓存配置

三、环境准备

首先需要在项目中添加必要的依赖:

<!-- Maven依赖 -->
<dependencies><!-- Spring Boot Starter Data Redis --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><!-- Spring Boot Starter Cache --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-cache</artifactId></dependency><!-- 连接池依赖 --><dependency><groupId>org.apache.commons</groupId><artifactId>commons-pool2</artifactId></dependency>
</dependencies>

然后在 application.properties 或 application.yml 中配置 Redis 连接信息:

# Redis配置
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=
spring.redis.database=0
# 连接池配置
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1ms
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0

四、启用 Redis 缓存注解

在 Spring Boot 应用的主类上添加@EnableCaching注解来启用缓存功能:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cache.annotation.EnableCaching;@SpringBootApplication
@EnableCaching // 启用缓存功能
public class Application {public static void main(String[] args) {SpringApplication.run(Application.class, args);}
}

五、配置 Redis 缓存管理器

为了让 Spring 使用 Redis 作为缓存提供者,我们需要配置 RedisCacheManager:

import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;import java.time.Duration;@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {@Beanpublic RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {// 默认缓存配置RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)) // 设置缓存过期时间为10分钟.serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())).disableCachingNullValues();return RedisCacheManager.builder(connectionFactory).cacheDefaults(defaultConfig).build();}
}

这段配置代码做了以下几件事:

  1. 创建一个 RedisCacheManager Bean
  2. 配置默认的缓存策略,包括:
  • 缓存过期时间为 10 分钟
  • 使用 StringRedisSerializer 序列化键
  • 使用 GenericJackson2JsonRedisSerializer 序列化值(以 JSON 格式存储)
  • 禁用缓存 null 值

六、使用缓存注解

现在我们可以在服务层使用缓存注解了。以下是一些常见的用法示例:

1. @Cacheable - 缓存查询结果

import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
public class UserService {// 使用@Cacheable注解,将方法返回值缓存到"users"缓存中// key使用SpEL表达式,基于方法参数生成@Cacheable(value = "users", key = "#id")public User getUserById(Long id) {System.out.println("从数据库查询用户ID: " + id);// 模拟从数据库查询return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));}// 条件缓存:只有当用户年龄大于18时才缓存@Cacheable(value = "users", key = "#id", condition = "#result.age > 18")public User getUserByIdWithCondition(Long id) {System.out.println("从数据库查询用户ID: " + id);return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));}// 除非条件:结果为null时不缓存@Cacheable(value = "users", key = "#id", unless = "#result == null")public User getUserByIdUnlessNull(Long id) {System.out.println("从数据库查询用户ID: " + id);return userRepository.findById(id).orElse(null);}
}

2. @CachePut - 更新缓存

import org.springframework.cache.annotation.CachePut;
import org.springframework.stereotype.Service;@Service
public class UserService {// 使用@CachePut更新缓存,无论缓存是否存在都会执行方法@CachePut(value = "users", key = "#user.id")public User updateUser(User user) {System.out.println("更新用户信息: " + user.getId());// 保存到数据库并返回更新后的对象return userRepository.save(user);}
}

3. @CacheEvict - 删除缓存

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;@Service
public class UserService {// 使用@CacheEvict删除缓存@CacheEvict(value = "users", key = "#id")public void deleteUser(Long id) {System.out.println("删除用户: " + id);userRepository.deleteById(id);}// 删除"users"缓存中的所有条目@CacheEvict(value = "users", allEntries = true)public void deleteAllUsers() {System.out.println("删除所有用户");userRepository.deleteAll();}// 删除后执行(删除数据库记录后再删除缓存)@CacheEvict(value = "users", key = "#id", beforeInvocation = false)public void deleteUserAfterInvocation(Long id) {System.out.println("删除用户: " + id);userRepository.deleteById(id);}
}

4. @Caching - 组合多个缓存操作

import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.CachePut;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;@Service
public class UserService {// 使用@Caching组合多个缓存操作@Caching(put = {@CachePut(value = "users", key = "#user.id"),@CachePut(value = "usersByName", key = "#user.name")},evict = {@CacheEvict(value = "recentUsers", allEntries = true)})public User complexUpdate(User user) {System.out.println("执行复杂更新: " + user.getId());return userRepository.save(user);}
}

5. @CacheConfig - 类级别的缓存配置

import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;@Service
@CacheConfig(cacheNames = "users") // 类级别缓存配置
public class UserService {// 无需指定value,继承类级别的cacheNames@Cacheable(key = "#id")public User getUserById(Long id) {System.out.println("从数据库查询用户ID: " + id);return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));}// 可以覆盖类级别的配置@Cacheable(value = "specialUsers", key = "#id")public User getSpecialUserById(Long id) {System.out.println("从数据库查询特殊用户ID: " + id);return userRepository.findById(id).orElseThrow(() -> new RuntimeException("User not found"));}
}

七、缓存键生成策略

Spring 默认使用 SimpleKeyGenerator 生成缓存键,但我们也可以自定义键生成策略:

import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.Arrays;@Component("myKeyGenerator")
public class MyKeyGenerator implements KeyGenerator {@Overridepublic Object generate(Object target, Method method, Object... params) {// 自定义键生成策略,例如:类名+方法名+参数return target.getClass().getSimpleName() + "_" + method.getName() + "_" + Arrays.deepHashCode(params);}
}

然后在注解中使用:

@Cacheable(value = "users", keyGenerator = "myKeyGenerator")
public User getUserById(Long id) {// ...
}

八、缓存配置进阶

1. 自定义缓存管理器

我们可以创建多个缓存管理器,用于不同的缓存需求:

@Bean
public RedisCacheManager customCacheManager(RedisConnectionFactory connectionFactory) {RedisCacheConfiguration config1 = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(5)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));RedisCacheConfiguration config2 = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofHours(1)).serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));return RedisCacheManager.builder(connectionFactory).withCacheConfiguration("shortTermCache", config1).withCacheConfiguration("longTermCache", config2).build();
}

2. 缓存穿透、缓存击穿和缓存雪崩解决方案

缓存穿透
// 使用@Cacheable的unless属性避免缓存null值
@Cacheable(value = "users", key = "#id", unless = "#result == null")
public User getUserById(Long id) {// 从数据库查询User user = userRepository.findById(id).orElse(null);// 对于不存在的用户,可以缓存一个特殊对象if (user == null) {// 可以记录到一个特殊的缓存中return null;}return user;
}
缓存击穿
// 使用sync属性,确保只有一个线程去加载数据
@Cacheable(value = "hotProducts", key = "#id", sync = true)
public Product getHotProduct(Long id) {// 从数据库加载热点数据return productRepository.findById(id).orElseThrow(() -> new RuntimeException("Product not found"));
}
缓存雪崩

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

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {// 使用随机过期时间,避免缓存雪崩Random random = new Random();RedisCacheConfiguration defaultConfig = RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10 + random.nextInt(5))) // 10-15分钟随机过期.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()));return RedisCacheManager.builder(connectionFactory).cacheDefaults(defaultConfig).build();
}

九、监控和调试

Spring Boot Actuator 提供了缓存监控端点:

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

启用缓存端点:

management.endpoints.web.exposure.include=cache*

访问以下端点查看缓存信息:

  • /actuator/caches - 获取所有缓存名称
  • /actuator/caches/{cacheName} - 获取指定缓存的信息
  • /actuator/caches/{cacheName}/{key} - 获取指定缓存中特定键的值

十、最佳实践

  1. 合理设置缓存过期时间:根据业务需求设置合适的过期时间,避免缓存数据长时间不更新
  2. 选择合适的缓存键:确保缓存键的唯一性和可读性
  3. 注意缓存一致性:在更新数据时及时更新缓存
  4. 避免缓存穿透:对不存在的数据也进行缓存
  5. 防止缓存雪崩:设置不同的过期时间,避免大量缓存同时失效
  6. 监控缓存使用情况:定期检查缓存命中率,优化缓存策略
  7. 考虑分布式环境:在分布式系统中,确保缓存更新操作的原子性
  8. 测试缓存逻辑:编写单元测试验证缓存行为

十一、总结

通过 @EnableCaching 注解和 Spring 的缓存抽象,我们可以非常方便地在 Java 应用中集成 Redis 缓存。这种声明式的缓存方式大大简化了代码,使我们能够专注于业务逻辑而不是缓存实现细节。

在实际应用中,我们需要根据业务特点合理配置缓存策略,注意缓存一致性问题,并采取措施防止缓存穿透、击穿和雪崩等常见问题。

相关文章:

  • 分布式MQTT客户端看门狗机制设计与实现
  • FOC电机三环控制
  • 蓝牙与MATLAB的无线通信实战指南:从基础到创新应用
  • Ubuntu下搭建Black Magic Probe (BMP) 编译环境
  • Flink读取Kafka写入Paimon
  • QT5中的QGraphics图形视图框架学习笔记(Item、Scene和View)
  • Modbus TCP转DeviceNet网关配置温控仪配置案例
  • git约定示提交
  • 浅谈DaemonSet
  • Jenkins 配置信息导出 的详细说明(中英对照)
  • 动态多目标进化算法:TrRMMEDA求解CEC2018(DF1-DF14),提供完整MATLAB代码
  • IOT集群扩容实践:问题剖析与解决策略
  • WebRTC(三):P2P协议
  • 企业不同发展阶段平衡品牌建设和利润获取的策略-中小企实战运营和营销工作室博客
  • 快速排序优化技巧详解:提升性能的关键策略
  • Linux 下 pcie 初始化设备枚举流程代码分析
  • 建筑业应用:机器人如何改变未来建筑业发展方向
  • 医疗行业网络安全的综合防护策略
  • 哈医大团队利用网络药理学+PPI分析+分子对接三联策略,解码灵芝孢子调控AKI凋亡的精准机制
  • 离线部署openstack 2024.1控制节点基础服务
  • 保险网站哪个好/今日最新财经新闻
  • 黄村网站建设一条龙/免费下载百度并安装
  • 广州白云区疫情最新消息2021/江门网站优化公司
  • 商务信息网站怎么做/东莞网站建设
  • 做付费推广是网站好还是单页面好/个人友情链接推广
  • wordpress首页排版错误/北京网站优化服务