您好,登录后才能下订单哦!
密码登录
登录注册
点击 登录注册 即表示同意《亿速云用户服务条款》
# SpringBoot中怎么使用Redis对单个对象进行自动缓存更新删除
## 目录
1. [Redis与SpringBoot集成概述](#redis与springboot集成概述)
2. [基础环境配置](#基础环境配置)
3. [实体类设计与序列化](#实体类设计与序列化)
4. [Repository层缓存实现](#repository层缓存实现)
5. [Service层缓存逻辑](#service层缓存逻辑)
6. [缓存自动更新机制](#缓存自动更新机制)
7. [缓存删除策略](#缓存删除策略)
8. [事务一致性处理](#事务一致性处理)
9. [高级特性与性能优化](#高级特性与性能优化)
10. [常见问题解决方案](#常见问题解决方案)
11. [完整代码示例](#完整代码示例)
12. [总结与最佳实践](#总结与最佳实践)
---
## Redis与SpringBoot集成概述
Redis作为高性能的键值存储系统,在SpringBoot中主要通过Spring Data Redis模块实现集成。对于单个对象的缓存管理,我们需要关注以下几个核心点:
1. **序列化方案**:选择适合业务场景的序列化方式(Jackson2Json/Java原生等)
2. **缓存粒度控制**:精确控制单个对象的缓存操作
3. **一致性保证**:确保数据库与缓存的数据一致性
4. **过期策略**:合理设置缓存过期时间
### 为什么需要对象级缓存?
- 避免频繁访问数据库获取相同数据
- 降低数据库负载压力
- 提升系统响应速度(Redis访问速度可达微秒级)
---
## 基础环境配置
### 1. 添加Maven依赖
```xml
<dependencies>
<!-- Spring Boot Starter Data Redis -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 连接池依赖 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!-- 如果使用JSON序列化 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
spring:
redis:
host: 127.0.0.1
port: 6379
password:
database: 0
lettuce:
pool:
max-active: 8
max-idle: 8
min-idle: 0
max-wait: -1ms
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(factory);
// 使用Jackson2JsonRedisSerializer序列化value
Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(Object.class);
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
om.activateDefaultTyping(om.getPolymorphicTypeValidator(),
ObjectMapper.DefaultTyping.NON_FINAL);
serializer.setObjectMapper(om);
// String序列化用于key
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(serializer);
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(serializer);
template.afterPropertiesSet();
return template;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User implements Serializable {
private static final long serialVersionUID = 1L;
private Long id;
private String username;
private String email;
private LocalDateTime createTime;
// 注意:实体类需要实现Serializable接口
// 如果使用JSON序列化则不需要
}
public class CacheKeyGenerator {
public static String generateEntityKey(Class<?> clazz, Object id) {
return String.format("%s::%s", clazz.getSimpleName(), id);
}
// 示例:生成User:1格式的key
public static String generateUserKey(Long userId) {
return generateEntityKey(User.class, userId);
}
}
@Repository
@CacheConfig(cacheNames = "user")
public interface UserRepository extends JpaRepository<User, Long> {
@Cacheable(key = "#id")
Optional<User> findById(Long id);
@CachePut(key = "#user.id")
default User saveWithCache(User user) {
return save(user);
}
@CacheEvict(key = "#id")
void deleteById(Long id);
}
@Component
public class UserCacheDao {
private static final String USER_KEY_PREFIX = "User::";
@Autowired
private RedisTemplate<String, Object> redisTemplate;
public void cacheUser(User user) {
String key = USER_KEY_PREFIX + user.getId();
redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
}
public Optional<User> getCachedUser(Long userId) {
String key = USER_KEY_PREFIX + userId;
return Optional.ofNullable((User)redisTemplate.opsForValue().get(key));
}
public void evictUser(Long userId) {
String key = USER_KEY_PREFIX + userId;
redisTemplate.delete(key);
}
}
@Service
@RequiredArgsConstructor
public class UserServiceImpl implements UserService {
private final UserRepository userRepository;
private final UserCacheDao userCacheDao;
@Override
@Transactional(readOnly = true)
public User getUserById(Long id) {
// 先查缓存
Optional<User> cachedUser = userCacheDao.getCachedUser(id);
if (cachedUser.isPresent()) {
return cachedUser.get();
}
// 缓存未命中则查数据库
User user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found"));
// 写入缓存
userCacheDao.cacheUser(user);
return user;
}
@Override
@Transactional
public User updateUser(User user) {
User updated = userRepository.save(user);
userCacheDao.cacheUser(updated); // 更新缓存
return updated;
}
@Override
@Transactional
public void deleteUser(Long id) {
userRepository.deleteById(id);
userCacheDao.evictUser(id); // 删除缓存
}
}
@Service
@CacheConfig(cacheNames = "user")
public class CacheAnnotatedUserService {
@Autowired
private UserRepository userRepository;
@Cacheable(key = "#id")
@Transactional(readOnly = true)
public User getById(Long id) {
return userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException("User not found"));
}
@CachePut(key = "#user.id")
@Transactional
public User update(User user) {
return userRepository.save(user);
}
@CacheEvict(key = "#id")
@Transactional
public void delete(Long id) {
userRepository.deleteById(id);
}
}
@Component
public class UserEntityListener {
@Autowired
private UserCacheDao userCacheDao;
@PostUpdate
@PostPersist
public void postUpdate(User user) {
// 实体更新后自动刷新缓存
userCacheDao.cacheUser(user);
}
@PostRemove
public void postRemove(User user) {
// 实体删除后自动清除缓存
userCacheDao.evictUser(user.getId());
}
}
// 定义事件
public class UserCacheEvictEvent extends ApplicationEvent {
private Long userId;
public UserCacheEvictEvent(Object source, Long userId) {
super(source);
this.userId = userId;
}
// getter...
}
// 事件监听器
@Component
public class UserCacheListener {
@Autowired
private UserCacheDao userCacheDao;
@EventListener
public void handleUserCacheEvict(UserCacheEvictEvent event) {
userCacheDao.evictUser(event.getUserId());
}
}
// 在Service中发布事件
@Service
public class UserEventService {
@Autowired
private ApplicationEventPublisher eventPublisher;
public void deleteUserWithEvent(Long id) {
// ...业务逻辑
eventPublisher.publishEvent(new UserCacheEvictEvent(this, id));
}
}
策略类型 | 优点 | 缺点 |
---|---|---|
主动删除 | 实时性强,一致性高 | 增加系统复杂度 |
被动过期 | 实现简单 | 存在短暂不一致 |
public void evictUserCache(Long userId) {
// 1. 删除本地缓存(如Caffeine)
localCache.invalidate(userId);
// 2. 删除Redis缓存
redisTemplate.delete(CacheKeyGenerator.generateUserKey(userId));
// 3. 发布缓存删除事件(用于集群环境)
redisTemplate.convertAndSend("cache_evict", "User:" + userId);
}
@Transactional
public void updateUserWithDoubleDelete(User user) {
// 第一次删除
userCacheDao.evictUser(user.getId());
// 更新数据库
userRepository.save(user);
// 延迟第二次删除
CompletableFuture.runAsync(() -> {
try {
Thread.sleep(500); // 延迟500ms
userCacheDao.evictUser(user.getId());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
}
@Transactional
public User transactionalUpdate(User user) {
// 1. 先更新数据库
User updated = userRepository.save(user);
// 2. 再更新缓存(在同一个事务中)
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
userCacheDao.cacheUser(updated);
}
});
return updated;
}
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 100))
public void safeCacheUpdate(User user) {
try {
userCacheDao.cacheUser(user);
} catch (Exception e) {
log.error("缓存更新失败,准备重试", e);
throw e; // 触发重试
}
}
@Recover
public void recoverCacheUpdate(Exception e, User user) {
log.error("缓存更新最终失败,记录日志等待人工处理", e);
// 可以将失败记录存入数据库或消息队列
}
public User getHotspotUser(Long id) {
String lockKey = "UserLock:" + id;
User user = userCacheDao.getCachedUser(id).orElse(null);
if (user == null) {
// 获取分布式锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 30, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
try {
// 双重检查
user = userCacheDao.getCachedUser(id).orElse(null);
if (user == null) {
user = userRepository.findById(id).orElseThrow();
userCacheDao.cacheUser(user);
}
} finally {
redisTemplate.delete(lockKey);
}
} else {
// 未获取到锁,短暂等待后重试
try {
Thread.sleep(100);
return getHotspotUser(id);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
}
return user;
}
@Component
public class UserBloomFilter {
private final BloomFilter<Long> bloomFilter;
public UserBloomFilter(RedisTemplate<String, Object> redisTemplate) {
this.bloomFilter = BloomFilter.create(
Funnels.longFunnel(),
1000000,
0.01);
}
public void addUser(Long id) {
bloomFilter.put(id);
}
public boolean mightContain(Long id) {
return bloomFilter.mightContain(id);
}
}
// 在Service中使用
public User getUserWithBloomFilter(Long id) {
if (!userBloomFilter.mightContain(id)) {
throw new EntityNotFoundException("User not exists");
}
return getUserById(id);
}
# application.yml 配置随机过期时间
spring:
cache:
redis:
time-to-live: 3600000 # 基础过期时间1小时
randomized-ttl-offset: 600000 # ±10分钟随机偏移
@Cacheable(value = "user", key = "#id",
unless = "#result == null") // 结果为null不缓存
public User getByIdNullable(Long id) {
return userRepository.findById(id).orElse(null);
}
@Bean
public RedisCacheConfiguration redisCacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.serializeValuesWith(SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer(objectMapper())))
.disableCachingNullValues();
}
@Bean
public ObjectMapper objectMapper() {
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FL_ON_UNKNOWN_PROPERTIES, false);
return mapper;
}
@Configuration
@EnableCaching
public class CacheConfig extends CachingConfigurerSupport {
@Bean
public CacheManager cacheManager(RedisConnectionFactory factory) {
return RedisCacheManager.builder(factory)
.cacheDefaults(RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofHours(1))
.transactionAware()
.build();
}
@Bean
public KeyGenerator userKeyGenerator() {
return (target, method, params) ->
"User::" + params[0];
}
}
@Service
public class CompleteUserService {
@Autowired
private UserRepository userRepository;
@Autowired
private UserCacheDao userCacheDao;
@Autowired
private UserBloomFilter userBloomFilter;
@Transactional(readOnly = true)
public User getCompleteUser(Long id) {
// 布隆过滤器检查
if (!userBloomFilter.mightContain(id)) {
throw new EntityNotFoundException();
}
// 尝试从缓存获取
return userCacheDao.getCachedUser(id)
.orElseGet(() -> {
User user = userRepository.findById(id)
.orElseThrow(() -> new EntityNotFoundException());
// 异步更新缓存
CompletableFuture.runAsync(() ->
userCacheDao.cacheUser(user));
return user;
});
}
@Transactional
public User updateCompleteUser(User user) {
User updated = userRepository.save(user);
// 事务提交后更新缓存
TransactionSynchronizationManager.registerSynchronization(
new TransactionSynchronization() {
@Override
public void afterCommit() {
userCacheDao.cacheUser(updated);
userBloomFilter.addUser(updated.getId());
}
});
return updated;
}
}
模式 | 适用场景 | 实现复杂度 |
---|---|---|
Cache-Aside | 大多数读多写少场景 | 中等 |
Read-Through | 需要抽象缓存层 | 较高 |
Write-Through | 强一致性要求 | 高 |
Write-Behind | 允许最终一致性 | 最高 |
// 通过Redis命令监控
Info stats // 查看命中率
Info memory // 内存使用情况
Slowlog get // 慢查询日志
通过合理的Redis缓存设计,可以使SpringBoot应用的性能提升10-100倍,特别是在高并发读取场景下。建议在实际项目中结合具体业务需求进行调整,并做好监控和容量规划。 “`
注:本文档实际字数约9500字,包含了从基础配置到高级优化的完整内容。实际使用时可根据项目需求选择适合的技术方案。
亿速云「云服务器」,即开即用、新一代英特尔至强铂金CPU、三副本存储NVMe SSD云盘,价格低至29元/月。点击查看>>
开发者交流群:
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。