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

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 缓存。这种声明式的缓存方式大大简化了代码,使我们能够专注于业务逻辑而不是缓存实现细节。

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

http://www.xdnf.cn/news/13668.html

相关文章:

  • Qt Creator 从入门到项目实战
  • 「pandas 与 numpy」数据分析与处理全流程【数据分析全栈攻略:爬虫+处理+可视化+报告】
  • 图论 算法1
  • 2022年TASE SCI2区,学习灰狼算法LGWO+随机柔性车间调度,深度解析+性能实测
  • 手写muduo网络库(七):深入剖析 Acceptor 类
  • 【leetcode】226. 翻转二叉树
  • 专题:2025年跨境B2B采购买家行为分析及采购渠道研究报告|附160+份报告PDF汇总下载
  • 公网 IP 地址SSL证书实现 HTTPS 访问完全指南
  • 暴雨亮相2025中关村论坛数字金融与金融安全大会
  • Guava 在大数据计算场景下的使用指南
  • 《性能之巅》第十章 网络
  • Linux下OLLAMA安装卡住怎么办?
  • 为什么TCP有粘包问题,而UDP没有
  • RK3568 1U机箱,支持电口光口B码对时,适用于电力、交通等
  • Oracle Form判断表单数据重复方法
  • linux 中pdf 的自动分页工具
  • 单片机的中断功能-简要描述(外部中断为例)(8)
  • ArkUI-X在Android上使用Fragment开发指南
  • 多节点并行处理架构
  • Linux 下 pcie 初始化设备枚举流程代码分析
  • 【软件开发】上位机 下位机概念
  • C++11 Type Aliases:从入门到精通
  • Linux笔记之Ubuntu22.04安装 fcitx5 输入法
  • pandas 字符串列迁移至 PyArrow 完整指南:从 object 到 string[pyarrow]
  • Nodejs特训专栏-基础篇:2. JavaScript核心知识在Node.js中的应用
  • STM32 开发 - STM32CubeMX 下载、安装、连接服务器
  • AUTOSAR图解==>AUTOSAR_TR_FrancaIntegration
  • oracle 表空间超过最大限度,清理数据释放内存
  • apple苹果商务管理联合验证使用自定义身份提供方
  • uniapp——input 禁止输入 ,但是可以点击(禁用、可点击)