SpringBoot结合Redis实现缓存管理

一、概述

本篇文章主要介绍spring boot缓存管理机制及相关概念,以及如何结合Redis实现缓存管理。

目标:

  • 熟悉Spring Cache基础组件及作用
  • 熟悉Spring Cache常用注解及作用
  • 掌握SpringBoot结合Redis实现缓存管理

二、 Spring Cache基础组件

  • keyGenerator

    keyGenerator 是用于生成缓存键(Cache Key)的组件。缓存键是用于在缓存中唯一标识缓存数据的值。默认情况下,Spring Cache 使用参数列表作为缓存键。但在某些情况下,如果需要自定义缓存键的生成逻辑,则可以创建自定义的 KeyGenerator 实现并配置到 Spring 中,如果没有自定义的话Spring提供默认的keyGenerator------SimplekeyGenerator,它会根据方法参数列表生成一个唯一的缓存 key。

  • cacheManager

    在 Spring Cache 框架中,CacheManager 是用于管理缓存实例的组件。它可以创建和管理多个缓存,每个缓存都有一个唯一的名称。当开发者在项目中配置了 Redis(或其他支持的缓存中间件),Spring 会自动使用相应的 CacheManager 实现,如 RedisCacheManager。 如果没有配置任何缓存中间件,Spring 默认会采用 SimpleCacheManager 作为缓存管理器。SimpleCacheManager 内部使用 ConcurrentHashMap 来维护缓存数据,它是一个线程安全的 HashMap 实现。这种情况下,并不需要额外的缓存中间件,Spring 会将缓存数据存储在内存中。

  • cacheResolver 和cacheManager作用一样,使用时二选一

三、 缓存管理注解

  • @EnableCaching

    该注解用于启用 Spring Framework 的缓存支持。通常在配置类上使用,表示开启缓存机制。有一个可选参数 proxyTargetClass,默认值为 false,表示使用 JDK 动态代理实现 AOP,如果设置为 true,则表示使用 CGLIB 代理进行 AOP。

  • @CacheConfig

    @CacheConfig 注解用于配置缓存的公共属性,如缓存名称、缓存管理器等。可以在类级别上使用,表示该类中的所有方法都具有相同的缓存规则。 该注解可选参数:

    • cacheNames:指定缓存名称。
    • keyGenerator:指定缓存 key 生成策略。
    • cacheManager:指定缓存管理器。
  • @CacheAble

    @Cacheable 注解用于标注方法的返回值可以被缓存,通常用于查询操作。如果缓存中存在对应的数据,则直接从缓存中获取数据返回,否则执行方法并将返回值存入缓存中。该注解可选参数:

    • cacheNames:指定缓存名称。

    • key:指定缓存 key,需要使用 SpEL 表达式进行动态计算,没有指定的话,会使用keyGenerator

      • cacheNamekey 可以组合成最终用于在 Redis 等缓存中存储数据的 key。通常情况下,cacheName 代表缓存的名称,而 key 则用于标识缓存中的具体数据。当使用 @Cacheable(cacheNames = "myCache", key = "#user.id") 这样的注解时,Spring 将会根据给定的 cacheNamekey 生成一个唯一的缓存 key,然后将方法返回的数据缓存到 Redis 中。
      • 例如,在 Redis 中可能会生成类似于 myCache::123 这样的键来存储缓存数据,其中 myCache 是缓存名称,123 则是根据 #user.id 表达式计算得出的具体键值。
    • condition:指定一个 SpEL 表达式,当条件为 true 时才会进行缓存操作。

    • unless:指定一个 SpEL 表达式,当条件为 false 时才会进行缓存操作。

      • condition属性是在方法执行前计算的,因此无法获取到方法返回结果。unless属性是在方法执行后计算的,因此可以拿到方法返回结果,即SpEL 表达式中可以使用#result获取到方法返回值。
    • sync:指定缓存是否需要同步更新。默认情况下,Spring Cache 不会对缓存进行同步更新,即在多线程环境下可能会出现缓存不一致的问题。但是,如果我们希望在缓存更新时进行同步操作,可以使用 sync 属性来实现。

      java 复制代码
      //当方法被多个线程并发调用时,只有一个线程能够执行方法并更新缓存,其他线程会被阻塞,直到更新完成后才能继续执行。
      @Cacheable(cacheNames = "myCache", key = "#id", sync = true)
      public User getUserById(Long id) {
          //....
      }
      //注意:使用 `sync` 属性会增加缓存访问的时间和资源消耗,因此建议只在必要的情况下使用。另外,对于高并发场景,使用 `sync` 属性可能会导致性能问题,因此需要谨慎使用。

    以下是常用的 SpEL 表达式及其说明:

    表达式 说明
    #root 代表被调用方法的参数列表
    #root.target 代表被调用方法的目标对象
    #root.caches 代表方法调用对应的缓存
    #root.methodName 代表被调用方法的名称
    #root.targetClass 代表被调用方法所在的类
    #result 代表方法调用的返回结果(仅在 @Cacheable 和 @CachePut 注解中有效)
    #argument 代表方法的参数,例如 #a0 代表第一个参数,#p0 也代表第一个参数
    T(...) 调用静态方法,比如 T(java.lang.Math).PI
    方法调用 直接调用方法,比如 hasPermission('read')
    集合访问 访问数组、列表、集合等元素,比如 list[0]
    属性访问 访问对象的属性,比如 [user.name]
  • @CachePut

    @CachePut 注解用于缓存标注方法的返回值,不管缓存中是否存在相同的键值,通常用于增加或更新操作。在方法执行后,将执行结果缓存到指定的缓存中。

    该注解可选参数:

    • cacheNames:同@Cacheable
    • key:同@Cacheable
    • condition: 同@Cacheable
    • unless:同@Cacheable
  • @CacheEvict

    @CacheEvict 注解用于标注方法执行后删除缓存中的数据,通常用于删除操作。

    该注解可选参数:

    • cacheNames:同@Cacheable
    • key:同@Cacheable
    • condition:同@Cacheable
    • allEntries:配合cacheNames一起使用,当 allEntries 属性设置为 true 时,表示清除缓存中缓存名称为cacheNames的所有条目。allEntries 默认值为false,表示只删除与该方法对应的缓存条目。
      • 例如:@CacheEvict(cacheNames = "k1",allEntries = true),会删除缓存中所有缓存名称为k1的键值对。
    • beforeInvocation:当 beforeInvocation 属性设置为 true 时,表示在方法执行之前清除缓存;即使方法执行出现异常,缓存仍然会被清除。当 beforeInvocation 属性设置为 false (默认值)时,表示在方法执行之后清除缓存;如果方法执行出现异常,缓存不会被清除。

四、 代码示例

添加pom依赖

xml 复制代码
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

数据缓存

java 复制代码
@Service
@CacheConfig(cacheNames = "user")
public class CacheService {

    @CachePut(key = "'userId:' + #p0.userId")
    public User addUser(User user){
        System.out.println("添加成功:user = " + user);
        return user;
    }

    @Cacheable(key = "'userId:' + #p0")
    public User getUser(Integer userId){
        User userFromDB = getUserFromDB(userId);
        System.out.println("查询成功: userId = " + userId);
        return userFromDB;
    }

    @CachePut(key = "'userId:' + #p0")
    public User update(Integer userId,User user){
        updateUserInDB(userId,user);
        System.out.println("更新成功:userId = " + userId);
        return user;
    }

    private void updateUserInDB(Integer userId,User user) {
        user.setUsername("DB_user");
    }

    private User getUserFromDB(Integer userId) {
        User user = new User();
        user.setUserId(userId);
        user.setUsername("DB_user");
        return user;
    }

    @CacheEvict(cacheNames = "k1",allEntries = true)
    public void del(int id){
    }
}

单元测试

typescript 复制代码
@SpringBootTest
public class CacheServiceTest {
    @Resource
    private CacheService cacheService;
    private User user;

    @BeforeEach
    void initUser(){
        user = new User();
        user.setUserId(1);
        user.setUsername("zhang san");
        user.setNickname("xiao san");
        user.setPhone("18788888888");
    }

    @Test
    void addCacheTest(){
        cacheService.addUser(user);
    }
    @Test
    void getCacheTest(){
        User user = cacheService.getUser(1);
        System.out.println("user = " + user);
    }

    @Test
    void updateCacheTest(){
        cacheService.update(1,user);
    }

    @Test
    void delCacheTest(){
        cacheService.del(2);
    }

    @Autowired
    private RedisTemplate redisTemplate;

    @Test
    public void key() {
        // 这里将缓存key都捞出来
        Set<String> keys = (Set<String>) redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
            Set<byte[]> sets = connection.keys("k*".getBytes());
            Set<String> ans = new HashSet<>();
            assert sets != null;
            for (byte[] b : sets) {
                ans.add(new String(b));
            }
            return ans;
        });
        System.out.println("keys = " + keys);
    }
}
相关推荐
why1516 小时前
微服务商城-商品微服务
数据库·后端·golang
結城9 小时前
mybatisX的使用,简化springboot的开发,不用再写entity、mapper以及service了!
java·spring boot·后端
星辰离彬9 小时前
Java 与 MySQL 性能优化:MySQL 慢 SQL 诊断与分析方法详解
java·spring boot·后端·sql·mysql·性能优化
q_19132846959 小时前
基于Springboot+Vue的办公管理系统
java·vue.js·spring boot·后端·intellij idea
陪我一起学编程10 小时前
关于nvm与node.js
vue.js·后端·npm·node.js
舒一笑11 小时前
基于KubeSphere平台快速搭建单节点向量数据库Milvus
后端
JavaBuild11 小时前
时隔半年,拾笔分享:来自一个大龄程序员的迷茫自问
后端·程序员·创业
一只叫煤球的猫12 小时前
虚拟线程生产事故复盘:警惕高性能背后的陷阱
java·后端·性能优化
周杰伦fans12 小时前
C#中用于控制自定义特性(Attribute)
后端·c#
Livingbody13 小时前
GitHub小管家Trae智能体介绍
后端