Spring Cache 缓存框架 + Redis 实战

19 阅读3分钟

1、Spring Cache

是 Spring 框架提供的缓存抽象层,提供统一的缓存接口注解方式,通过一组简单的注解,能够轻松为应用程序添加缓存功能,而无需关注底层缓存的具体实现。

  • 支持多种缓存实现(Redis,EhCache等)
  • 通过注解就能控制缓存行为
  • 不依赖某一种具体缓存,可以切换缓存而不更改业务逻辑

1.1 缓存注解

  • @Cacheable:缓存查询,用于标记一个方法,表示其返回值可以缓存。
//标记一个方法的返回值可以被缓存
//缓存的键和值可以自定义
@Cacheable(value = "users", key = "#userId")
public User getUserById(String userId) {
    // 这里是查询用户的业务逻辑
    return userRepository.findById(userId);
}
  • @CachePut:更新缓存,用于方法执行后更新缓存(即使缓存已有数据,也会执行方法并更新缓存)。
//该注解标记的方法总是执行,并且其返回值会更新到缓存中
@CachePut(value = "users", key = "#user.id")
public User updateUser(User user) {
    // 更新用户的逻辑
    return userRepository.save(user);
}
  • @CacheEvict:用于清除缓存。
//清除缓存,可以是指定缓存的某个值,也可以清除整个缓存
@CacheEvict(value = "users", key = "#userId")
public void deleteUser(String userId) {
    // 删除用户的业务逻辑
    userRepository.deleteById(userId);
}
  • @Caching:组合注解,复杂缓存策略可组合使用

1.2 缓存存储和策略

  • Spring 提供了 CacheManager 接口,支持多种不同的缓存实现,可以通过配置指定缓存的存储方式。

2、配置Spring Cache + Redis

2.1 添加依赖 pom.xml

<!-- Spring Cache 核心依赖 -->
<dependency> 
    <groupId>org.springframework.boot</groupId> 
    <artifactId>spring-boot-starter-cache</artifactId> 
</dependency>
<!--  Redis -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

2.2 配置Redis连接 application.yml

spring:
  cache:
    type: redis # 使用Redis 作为缓存
  #Redis配置
  redis:
    host: localhost
    port: 6379
    password: #没有密码留空

2.3 开启缓存支持

使用 @EnableCaching 注解启用缓存, 在启动类或者配置类上添加注解。

2.4 配置缓存管理器:Redis CacheManager

Spring Boot 默认会自动配置 RedisCacheManager,但如果想自定义缓存过期时间、序列化方式,可以自己配置。

package com.example.config;

import org.springframework.cache.CacheManager;
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 {

    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory){
        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
                .entryTtl(Duration.ofMinutes(30))  //默认过期时间
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer())) //指定缓存Key的序列化方式
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer()))
                .disableCachingNullValues();  //禁止缓存null值

        return RedisCacheManager.builder(redisConnectionFactory)
                .cacheDefaults(config)
                .build();
    }
}

2.5 在 Service 层使用缓存注解

使用@Cacheable、@CacheEvict
改造DepartmentService,替换原本使用的 Redis缓存(Redis集成与基础操作1、Redis是什么 高性能内存数据库 常用于:缓存(Cache),Session存储,分布式锁 - 掘金)

@Service
public class DepartmentServiceImpl implements DepartmentService {

    @Autowired
    private DepartmentMapper departmentMapper;

    // 使用Spring Cache注解
    @Cacheable(value = "departments", key = "'all'")
    @Override
    public List<Department> findAll() {
        return departmentMapper.selectList(null);
    }

    @Cacheable(value = "departments", key = "#id")
    @Override
    public Department findById(Integer id) {
        return departmentMapper.selectById(id);
    }

    @CacheEvict(value = "departments", allEntries = true)  // 清空所有科室缓存
    @Override
    public boolean updateDepartment(Department department) {
        int rows = departmentMapper.updateById(department);
        return rows > 0;
    }
}

分页查询缓存

@Service
public class AppointmentServiceImpl implements AppointmentService {

    // 分页查询缓存(注意:分页参数影响key)
    @Cacheable(value = "appointments", key = "'user:' + #userId + '_page:' + #pageNum + '_size:' + #pageSize")
    @Override
    public IPage<Appointment> findAppointmentsByUserId(Integer userId, Integer pageNum, Integer pageSize) {
        Page<Appointment> page = new Page<>(pageNum, pageSize);
        LambdaQueryWrapper<Appointment> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(Appointment::getUserId, userId)
               .orderByDesc(Appointment::getAppointmentDate);
        return appointmentMapper.selectPage(page, wrapper);
    }

    // 预约操作后清除该用户的预约缓存
    @CacheEvict(value = "appointments", key = "'user:' + #userId + '_page:*'", allEntries = true)
    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean bookAppointment(Integer userId, Integer deptId, String appointmentDate) {
        // ... 原业务逻辑不变
    }
}