文章介绍

继myabtis二级缓存整合redis之后,利用课余时间又研究了一下Spring Cache整合redis。原本是计划将这两篇文章和redis的介绍整合成一篇文章,但是。。。手贱的我在整合过程中不小心点击了刷新,导致新写的内容被清空了。花了我两天的宝贵时间啊。。。。。。
在这里插入图片描述
额额额,这一篇是继删除之后,根据回忆,又重写了一篇。虽然原文档丢了,但是还好,参考的blog,笔记浏览器里面还保存着,不至于让我太过难受,好了。拿重点。

本篇主要从原生spring cache实现缓存操作,背后源码浅析,再到redis整合。最后又通过fastjson进行序列化,使保存在redis中的内容不至于乱码。

Spring Cache基本使用

简介

  1. Spring Cache将缓存作用于方法上,在方法的执行后缓存方法的返回内容
  2. 缓存数据时,默认以类名+方法名+参数以键,以方法的返回值为value进行缓存(当然,key可以自定义)

常用注解解释

下面的注解解释中,为了演示方便,我集成了redis分布式缓存,大家可以看到缓存的信息。即使不使用reids缓存,下方代码照样可以正常执行。文章在之后会介绍如何整合redis分布式缓存。
在这里插入图片描述

1.@CacheConfig

定义在类上,通常使用cacheNames,定义之后,该类下的所有含缓存注解的key之前都会拼接其属性值(附带两个::)。适用于某一个service的实现类或者mapper。

注意:如果service和mapper有联系时,比如都操纵的App这个entity,则service上的cacheNames会覆盖掉mapper

可定义的属性

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CacheConfig {
	String[] cacheNames() default {};
	String keyGenerator() default "";
	String cacheManager() default "";
	String cacheResolver() default "";
}

举例:

serviceImpl

@Service
@Transactional    //控制事务
@AllArgsConstructor //代替@Autowired
@CacheConfig(cacheNames = "app")
public class AppServiceImpl implements IAppService {

    private final AppDAO appDAO;
    @Override
    @Cacheable(key = "'id:'+#id") //当结果的name属性为zhq时,不进行缓存
    public App findOne(Long id) {
        App app = appDAO.findOne(id);
        return app;
    }
}
@Repository
@CacheConfig(cacheNames = "appDao")
public interface AppDAO extends BaseMapper<App> {
    List<App> findAll();
    App findOne(Long id);
    void deleteOne(Long id);
}

效果:
在这里插入图片描述从上面的运行结果我们可以看出,虽然我们在serviceImpl都定义了CacheCongfig,并且是不同的cacheNames,但是最后还是显示的serviceImpl上的cacheNames。

2.@Cacheable

  • 定义在方法上,待方法运行结束时,缓存该方法的返回值。

  • 每次执行该方法前,会先去缓存中查有没有相同条件下,缓存的数据,有的话直接拿缓存的数据,没有的话执行方法,并将执行结果返回。

  • 默认以类名+方法名+参数为key,返回值为value

5个常用属性

key:

可以为null
存储在缓存中的键,可以根据它获取响应的值。默认是类名+方法名+参数。
可以通过SpEL进行自定义

默认:
在这里插入图片描述
自定义:
在这里插入图片描述
多参数时,某些参数可能不适合做key,此时我们就可以指定参数进行缓存。

@Cacheable(cacheNames="books", key="#isbn")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
 
@Cacheable(cacheNames="books", key="#isbn.rawNumber")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
 
@Cacheable(cacheNames="books", key="T(someType).hash(#isbn)")
public Book findBook(ISBN isbn, boolean checkWarehouse, boolean includeUsed)
 
@Cacheable(cacheNames="books", key="#map['bookid'].toString()")
public Book findBook(Map<String, Object> map)

value/cacheNames:

不能为null
缓存名称,二者选任意一个即可。
在这里插入图片描述
源码中使用了@AliasFor注解,链接了cacheNames,这是一个别名注解。意味着value和cacheNames可以互相替换。当类上使用@ConfigConfig{cacheNames}定义时,cacheable中的cacheNames或者value会将其替代。
在这里插入图片描述当我们定义多个cacheNames时,会给我们生成多个缓存名称,我们进行查询时,他也会查两个缓存文件下的数据。
在这里插入图片描述

condition:
根据条件判断结果是否缓存
默认为true 缓存

执行语句

    public void testRedis(){
        appService.findOne(8L);
        appService.findOne(9L);
    }

在这里插入图片描述

unless:

不被缓存的条件,默认为false。即能执行的都被缓存
id大于8的将不被缓存

    public void testRedis(){
        appService.findOne(8L);
        appService.findOne(9L);
    }

在这里插入图片描述condition和unless的区别。

  1. condition默认为true,unless默认为false。
  2. condition为false时,unless为true。不被缓存
  3. condition为false,unless为false。不被缓存
  4. condition为true,unless为true。 不被缓存
  5. conditon为true,unless为false。缓存
@Cacheable(key = "'deptId:'+#deptId",unless="#result == null")    设置当结果为null,则不进行缓存

sync
是否同步,true/false。在一个多线程的环境中,某些操作可能被相同的参数并发地调用,这样同一个 value 值可能被多次计算(或多次访问 db),这样就达不到缓存的目的。针对这些可能高并发的操作,我们可以使用 sync 参数来告诉底层的缓存提供者将缓存的入口锁住,这样就只能有一个线程计算操作的结果值,而其它线程需要等待,这样就避免了 n-1 次数据库访问。

sync = true 可以有效的避免缓存击穿的问题。
在这里插入图片描述

3.@CachePut

和Cacheablle有相同的属性(没有 sync 属性),通常用于更新操作。

@Cacheable 的逻辑是:查找缓存 - 有就返回 -没有就执行方法体 - 将结果缓存起来;

@CachePut 的逻辑是:执行方法体 - 将结果缓存起来;

注意:@Cacheable 和 @CachePut 注解到同一个方法。

    @Override
    @CachePut(key = "'id:'+#app.appId")
    public App updateApp(App app) {
        appDAO.updateByPrimaryKey(app);
        return app;
    }
@Test
    public void testInsert() {
        //増
        App app = new App();
        app.setAppId(null);
        app.setName("spring缓存测试");
        app.setDescription("第一次测试");
        appService.saveApp(app);
        //查
        System.out.println("第一次查询"+ appService.findOne(app.getAppId()));
        //再次查
        System.out.println("第二次查询"+appService.findOne(app.getAppId()));
        //改
        app.setName("更新项目");
        appService.updateApp(app);
        System.out.println("更新后"+appService.findOne(app.getAppId()));
    }

在这里插入图片描述

4.@CacheEvit

删除缓存,每次调用它注解的方法,就会执行删除指定的缓存

跟 @Cacheable 和 @CachePut 一样,@CacheEvict 也要求指定一个或多个缓存,也指定自定义一的缓存解析器和 key 生成器,也支持指定条件(condition 参数)

CacheEvit,有两个特有的属性:allEntries和beforeInvocation
allEntries:
默认为false,为true时,表示清空该cachename下的所有缓存
beforeInvocation:
默认为false,为true时,先删除缓存,再删除数据库。

    //先走缓存,并且删除所有改cachename下的内容
    @Override
    @CacheEvict(key = "'id:'+#id",beforeInvocation =true ,allEntries = true)
    public void deleteOne(Long id) {
        appDAO.deleteOne(id);
    }

测试

	@Test
    public void testRedis(){
        appService.findOne(8L);
        appService.findOne(9L);
    }

在这里插入图片描述
app下有两条记录,我们再执行删除。

 @Test
    public void  testDelete(){
      //删除
      appService.deleteOne(8L);
    }

在这里插入图片描述
虽然我们删除的是8L,但是因为我们经过了@CacheEvit这个注解标注的方法,并且属性时allEntries,所以我们会清空缓存。

**注意:**因为缓存和数据库的执行顺序不同,很可能我们当删完数据库/或缓存其中一个时,服务器出现异常,导致其中一个没有删除。

CacheEvit的使用及情况分析

(我的解决思路:重要的数据不使用CacheEvit维护,使用逻辑维护缓存,之后会在用后演示如何用逻辑维护缓存)

5.@Caching

组合 注解,有时候我们可以一个方法上定义多个缓存注解。
比如:一个添加的方法
我添加一个内容,并缓存这个添加的内容。(Cacheable)
当我添加新的内容时,我要先清空缓存或清除某一个缓存。(CacheEvit)

@Caching(cacheable = {
        @Cacheable(value = "emp",key = "#p0"),
        ...
},
put = {
        @CachePut(value = "emp",key = "#p0"),
        ...
},evict = {
        @CacheEvict(value = "emp",key = "#p0"),
        ....
})
public User save(User user) {
    ....
}
在这里插入代码片

6.@EnableCaching

会自动扫描所有public中包含缓存的相关注解,使用来开启缓存的。

可以定义在缓存的配置类中(之后在解释),也可以配置在入口类中。

两个接口

1.CacheManager

缓存管理器,用于管理缓存组件。

cacheManager接口的作用是用来获取Cache,类似一种对象工厂,所有的Cache,必须依赖与CacheManager来获取
在这里插入图片描述
这里以redis缓存进行测试

 @Autowired
    CacheManager cacheManager;
    @Test
    public void getCacheBean(){
        //获取我们定义的cacheNames
        Collection<String> cacheNames = cacheManager.getCacheNames();
        cacheNames.forEach(item->{
            System.out.println(item);   
           // app:
		  // user:
        });
        //获取RedisCacheManager   名称默认以第一个字母小写。
        Cache redisCacheManager = cacheManager.getCache("redisCacheManager");
        System.out.println(redisCacheManager.getName());//redisCacheManager
    }

缓存的形式有很多,每一种缓存的实现,都依靠一个缓存管理器,来配置该缓存的基本信息(序列化方法,缓存数据的过期时间等。)但是我们使用时,通常只使用一种。

默认情况下,spring为我们提供了这些缓存处理接口,当我们引入redis依赖时,他会再生成一个RedisCacheManager,上面的案例就是引入了redis的依赖。
在这里插入图片描述

2.cacheResolver

CacheResolver,缓存解析器是用来管理缓存管理器的,CacheResolver 保持一个 cacheManager 的引用,并通过它来检索缓存。CacheResolver 与 CacheManager 的关系有点类似于 KeyGenerator 跟 key。spring 默认提供了一个 SimpleCacheResolver,开发者可以自定义并通过 @Bean 来注入自定义的解析器,以实现更灵活的检索。
在这里插入图片描述

大多数情况下,我们的系统只会配置一种缓存,所以我们并不需要显式指定 cacheManager 或者 cacheResolver。但是 spring 允许我们的系统同时配置多种缓存组件,这种情况下,我们需要指定。指定的方式是使用 @Cacheable 的 cacheManager 或者 cacheResolver 参数。

注意:按照官方文档,cacheManager 和 cacheResolver 是互斥参数,同时指定两个可能会导致异常。

Spring默认本地缓存实现

默认情况下,spring默认使用的是SimpleCacheConfiguration,即使用ConcurrentMapCacheManager来实现缓存。
在这里插入图片描述我们发现,在SimpleCacheConfiguration中,缓存的配置只对cacheNames进行了相关设置。对于缓存的过期时间,最大缓存数量等都没有进行设置。

也就是说在默认缓存的情况下,缓存的功能是比较局限的,我们需要手动配置。

同样,如果使用redis进行缓存时,我们需要对RedisCacheManager进行配置(之后会讲解)。

快速上手

使用步骤:

  1. 引入依赖
        <!--        springcache-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
  1. 添加cache启动缓存注解EnableCaching
@SpringBootApplication
@MapperScan("com.cache.mycache.dao")
@EnableCaching
public class MycacheApplication {

    public static void main(String[] args) {
        SpringApplication.run(MycacheApplication.class, args);
    }

}
  1. 相应位置添加缓存注解
@Service
@Transactional    //控制事务
@AllArgsConstructor //代替@Autowired
@CacheConfig(cacheNames = "app")
public class AppServiceImpl implements IAppService {

    private final AppDAO appDAO;
    @Override
    @Cacheable(key = "'id:'+#id",condition = "#id>8")
        public App findOne(Long id) {
        App app = appDAO.findOne(id);
        return app;
    }

    @Override
    public List<App> findAll() {
        List<App> allApp = appDAO.findAll();
        System.out.println(allApp);
        return allApp;
    }
    
    //先走缓存,并且删除所有改cachename下的内容
    @Override
    @CacheEvict(key = "'id:'+#id",beforeInvocation =true ,allEntries = true)
    public void deleteOne(Long id) {
        appDAO.deleteOne(id);
    }

    @Override
//    @CachePut(key = "'id:'+#app.getAppId()")
    public void saveApp(App app) {
        appDAO.insert(app);
    }


    @Override
    @CachePut(key = "'id:'+#app.appId")
    public App updateApp(App app) {
        appDAO.updateByPrimaryKey(app);
        return app;
    }
    
    //自定义的清除所有缓存的方法
    @Override
    @CacheEvict(allEntries = true)
    public void clearCache(){
    }

}

  1. 测试使用

先添加,再进行两次同一条数据查询,再更新。我们再查一个其他id的数据,此时缓存中应该有两条数据。最后执行删除,因为我们删除时,设置的CacheEvit的allEntries为true,应该是删除所有数据。此时我们应该再进行数据库查,而非缓存查。

  @Test
    public void testInsert() {
        //増
        App app = new App();
        app.setAppId(null);
        app.setName("spring缓存测试");
        app.setDescription("第一次测试");
        appService.saveApp(app);
        //查
        System.out.println("第一次查询"+ appService.findOne(app.getAppId()));
        //再次查
        System.out.println("第二次查询"+appService.findOne(app.getAppId()));
        //改
        app.setName("更新项目");
        appService.updateApp(app);
        System.out.println("更新后"+appService.findOne(app.getAppId()));

        //我们再查一个id为35的。此时会缓存两条数据,一条是我们刚刚新建立的那个app的id,一个是35
        //删除掉35的id
        appService.deleteOne(35L);
        //查新建立的appid
        System.out.println("清空缓存之后,再次查"+appService.findOne(app.getAppId()));
    }

在这里插入图片描述

总结

通过原生cache的使用,我们发现其弊端很明显,因为他同mybatis默认的二级缓存一样,数据是存储在应用服务器上,当我们项目重启时,他就会删除掉之前的所有缓存。对于中大型项目会非常的不适用。

Spring Cache整合Redis

之前在CacheManager介绍那里,我们知道,当我们使用一种缓存方式时,必须定义一个缓存的管理器,来操作我们的缓存,并设置一些基本的信息,满足的需求。所以,我们整合redis,实现自定义缓存管理,也得从这两个方面入手。一个是缓存管理器,一个是缓存的配置信息。

整合步骤

1.引入redis和cache依赖
        <!--        redis缓存-->
        <dependency>
        <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <!--        springcache-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-cache</artifactId>
        </dependency>
2.配置redis的相关信息
# Redis数据库索引(默认为0)
spring.redis.database=0
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=
# 连接池最大连接数(使用负值表示没有限制)
spring.redis.jedis.pool.max-active=20
# 连接池最大阻塞等待时间(使用负值表示没有限制)
spring.redis.jedis.pool.max-wait=-1
# 连接池中的最大空闲连接
spring.redis.jedis.pool.max-idle=10
# 连接池中的最小空闲连接
spring.redis.jedis.pool.min-idle=0
# 连接超时时间(毫秒)
spring.redis.timeout=1000
3.配置缓存管理器

创建RedisConfig类,继承 CachingConfigurerSupport 类。
在这里插入图片描述

CachingConfigurerSupport :继承了CachingConfiguer接口。这个接口为我们提供了四个方法,我们可以进行相关的配置。
配置方式一:完全自定义配置(本文的方式)

重新创建一个RedisCacheManager,定义其主键生成策略,基本配置,以及错误处理接口
基本配置在RedisCacheManager中设置,我们不采用bean注入的方式设置redis缓存管理的基本配置,通过RedisCacheManager的cacheDefaults方法进行设置。。

使用自定义的RedisCacheManager,我们可以更自由的设置其属性,比如我们可以根据不同的cacheNames从而设置不同的过期时间。

期间我们使用了fastjson进行序列化,这样我们通过注解往缓存中存储数据就不会乱码了

//缓存管理器。可以管理多个缓存
    //只有CacheManger才能扫描到cacheable注解
    //spring提供了缓存支持Cache接口,实现了很多个缓存类,其中包括RedisCache。但是我们需要对其进行配置,这里就是配置RedisCache
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheManager cacheManager = RedisCacheManager.RedisCacheManagerBuilder
                //Redis链接工厂
                .fromConnectionFactory(connectionFactory)
                //缓存配置 通用配置  默认存储一小时
                .cacheDefaults(getCacheConfigurationWithTtl(Duration.ofHours(1)))
                //配置同步修改或删除  put/evict
                .transactionAware()
                //对于不同的cacheName我们可以设置不同的过期时间
                .withCacheConfiguration("app:",getCacheConfigurationWithTtl(Duration.ofHours(5)))
                .withCacheConfiguration("user:",getCacheConfigurationWithTtl(Duration.ofHours(2)))
                .build();
        return cacheManager;
    }
    //缓存的基本配置对象
    private   RedisCacheConfiguration getCacheConfigurationWithTtl(Duration duration) {
        return RedisCacheConfiguration
                .defaultCacheConfig()
                //设置key value的序列化方式
                // 设置key为String
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 设置value 为自动转Json的Object
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
                // 不缓存null
                .disableCachingNullValues()
                // 设置缓存的过期时间
                .entryTtl(duration);
    }

配置方式二:
使用原有的RedisCacheManager。

这种方式特别简单,不创建新的RedisCacheManager的Bean对象,通过RedisCacheConfiguration来设置缓存的基本设置。

默认过期时间是2个小时

    @Bean
    public RedisCacheConfiguration redisCacheConfiguration() {
        FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);
        RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig();
        configuration =
                configuration.serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer)).entryTtl(Duration.ofHours(2));
        return configuration;
    }

默认RedisCacheManager的源代码
在这里插入图片描述

4.自定义主键key的生成策略

当我们不设置主键时,主键的生成策略

//主键生成策略  不设置主键时的生成策略  类名+方法名+参数
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params
                ) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }
5.缓存的异常处理

当进行缓存出现异常时,不进行缓存操作

 //缓存的异常处理
    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        // 异常处理,当Redis发生异常时,打印日志,但是程序正常走
        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                log.error("Redis occur handleCacheClearError:", e);
            }
        };
    }
6.重新定义redis操作模板

spring为我们提供了RedisTemplate和StringRedisTemplate,但是因为他们存储数据的值默认是使用jdk进行序列化的,存储的时候是以2进制的形式进行存储,不便于我们观看。
我们采用FastJson进行序列化,来存储便于我们观看的对象信息。

   //操纵缓存的模板
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        System.out.println("redisTemplate");
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }
    //操纵缓存的模板
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        System.out.println("stringTemplate");
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
        stringRedisTemplate.setValueSerializer(fastJsonRedisSerializer);
        stringRedisTemplate.setConnectionFactory(factory);
        stringRedisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
        return stringRedisTemplate;
    }
7.使用FastJson解决缓存的乱码问题

我们知道,缓存默认使用的是jdk序列化,它是以二进制的形式往缓存中存储数据的,导致我们存储的数据成乱码的形式。

        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
        </dependency>
//重写FastJsonRedisSerialize的实现方式。
class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    private ObjectMapper objectMapper = new ObjectMapper();
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static {
        // 全局开启AutoType,这里方便开发,使用全局的方式
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        // 建议使用这种方式,小范围指定白名单
        // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.domain");
        // key的序列化采用StringRedisSerializer
    }

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    //序列化 我们存储时,存储的是json对象,而默认存储的是byte类型的,所以在可视化窗口上显示时,看到的是乱码
    @Override
    public byte[] serialize(T t) throws SerializationException {
        System.out.println("进行序列化");
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    //反序列化
    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return JSON.parseObject(str, clazz);
    }
}

8.测试使用
    @Test
    public void testInsert() {
        //増
        App app = new App();
        app.setAppId(null);
        app.setName("spring缓存测试");
        app.setDescription("第一次测试");
        appService.saveApp(app);
        //查
        System.out.println("第一次查询"+ appService.findOne(app.getAppId()));
        //再次查
        System.out.println("第二次查询"+appService.findOne(app.getAppId()));
        //改
        app.setName("更新项目");
        appService.updateApp(app);
        System.out.println("更新后"+appService.findOne(app.getAppId()));

        //我们再查一个id为35的。此时会缓存两条数据,一条是我们刚刚新建立的那个app的id,一个是35
        //删除掉35的id
        appService.deleteOne(35L);
        //查新建立的appid
        System.out.println("清空缓存之后,再次查"+appService.findOne(app.getAppId()));
    }

在这里插入图片描述

我们发现,除了配置,其他的使用都和原生的cache一模一样。只是我们是分布式缓存,我们重启应用服务时,缓存中的数据并不会清空。

最后一次我们查的是41,我们这次重启服务,直接查一下41

       System.out.println(appService.findOne(41L));

在这里插入图片描述
没有走数据库,直接查询的缓存。
在这里插入图片描述

9…自定义缓存维护

之前我们的缓存实现,都是通过spring提供的缓存注释完成的。虽然能完成某条缓存的增删改,但是我们也发现,以上的情况我们都是用于单表的情况下。

假如我们在下面这个场景中:

我们要获取一个人员及其所在的部门。人员和部门各在一张表。
(user表的dept_id指向dept表的id)

SELECT u.`id`,u.`nick_name`,d.`id` AS deptId ,d.`name` 
FROM sys_user u,sys_dept d 
WHERE u.dept_id = d.id 

这是我们查出来的信息。
在这里插入图片描述
我们往缓存中存储时,以user的id为key进行存储

key:id:3
value: { “dept”: {“id”: 18,“name”: “家族一期” }, “id”: 3, “name”: “光亮”}

此时问题来了,如果我们修改了用户的信息,我们使用CacheEvit删除掉当前的用户即可。再次查询时,再把他存入缓存。但是如果我们修改了部门的信息,那么我们该如何进行缓存维护呢?

serviceimpl

    userServiceImpl

    @Override
    @Cacheable(key = "'id:'+#id")
    public UserDTO findUserAndDeptById(Long id) {
        return userDAO.findUserAndDeptById(id);
    }
    
   deptServiceImpl
    @Override
    public void deleteOne(Long id) {
        deptDAO.deleteOne(id);
    }

mapper.xml

 <select id="findUserAndDeptById" parameterType="com.cache.mycache.entity.dto.UserDTO" resultMap="userMessage">
    SELECT u.`id`,u.`nick_name`,d.`id` AS deptId ,d.`name` FROM sys_user u,sys_dept d WHERE u.dept_id = d.id and u.id=#{id}
    </select>

    <resultMap id="userMessage" type="com.cache.mycache.entity.dto.UserDTO">
        <id property="id" column="id" />
        <result property="name" column="nick_name"/>
        <association property="dept" javaType="com.cache.mycache.entity.Dept">
            <result property="id" column="deptId"/>
            <result property="name" column="name"/>
        </association>
    </resultMap>

测试:

    @Test
    public  void  testUserAndDept(){
         //查询进缓存
        UserDTO userAndDeptById = userService.findUserAndDeptById(3L);
        System.out.println("在缓存中查询"+userService.findUserAndDeptById(3L));
        //删除部门
        deptService.deleteOne(userAndDeptById.getDept().getId());
    }

在这里插入图片描述
查看缓存:

在这里插入图片描述我们发现,我们的部门已经删除,但是缓存中的数据还是原始的数据。

此时凭借缓存注解,貌似已经很难完成我们的业务了。所以我们只能靠逻辑来处理了。

分析:

用户基本信息+部门信息 ( 键为用户id)

删除/更新该用户:使用注解CacheEvid,指向该id,删除缓存记录。

  @Override
    @CacheEvict(key = "'id:'+#id")
    public void deleteOne(Long id) {
   userDAO.deleteOne(id);
    }

删除/更新部门:通过逻辑获取该部门中的所有人员id,清空该部门下所有以该人员id为键的缓存信息(因为一个部门有很多人员,可能不止一个人缓存了基本信息和部门信息)

删除部门
    @Override
    public void deleteDept(Long id) {
        //获取该部门下的所有员工
        List<Long> userByDeptId = userService.findUserByDeptId(id);
        userByDeptId.forEach(item->redisTemplate.delete("user::id:"+item));
        deptDAO.deleteOne(id);
    }

在这里插入图片描述我们查询id为1的数据,之后再查一遍看是否进缓存,之后再删除部门7,看看缓存中的数据是否清除

    @Test
    public  void  testUserAndDept(){

            //查询进缓存
            UserDTO userAndDeptById = userService.findUserAndDeptById(1L);
            System.out.println("在缓存中查询"+userService.findUserAndDeptById(1L));
            //删除部门
            deptService.deleteOne(userAndDeptById.getDept().getId());

    }

在这里插入图片描述查看缓存

在这里插入图片描述
我们发现,缓存中的内容已经成功删除。
我们上面演示了删除操作,更新操作同理。

附:

SpringCache整合redis配置类
@Slf4j
@Configuration
@EnableCaching
@ConditionalOnClass(RedisOperations.class)   // 该配置类执行的条件是,RedisOperations的bean对象已经存在
@EnableConfigurationProperties(RedisProperties.class)   //使RedisProperties的@ConfigurationProperties生效
public class RedisConfig extends CachingConfigurerSupport {

    private  static  final  FastJsonRedisSerializer<Object> fastJsonRedisSerializer = new FastJsonRedisSerializer<>(Object.class);


    //主键生成策略  不设置主键时的生成策略  类名+方法名+参数
    @Override
    @Bean
    public KeyGenerator keyGenerator() {
        return new KeyGenerator() {
            @Override
            public Object generate(Object target, Method method, Object... params) {
                StringBuffer sb = new StringBuffer();
                sb.append(target.getClass().getName());
                sb.append(method.getName());
                for (Object obj : params
                ) {
                    sb.append(obj.toString());
                }
                return sb.toString();
            }
        };
    }

    //缓存管理器。可以管理多个缓存
    //只有CacheManger才能扫描到cacheable注解
    //spring提供了缓存支持Cache接口,实现了很多个缓存类,其中包括RedisCache。但是我们需要对其进行配置,这里就是配置RedisCache
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        RedisCacheManager cacheManager = RedisCacheManager.RedisCacheManagerBuilder
                //Redis链接工厂
                .fromConnectionFactory(connectionFactory)
                //缓存配置 通用配置  默认存储一小时
                .cacheDefaults(getCacheConfigurationWithTtl(Duration.ofHours(1)))
                //配置同步修改或删除  put/evict
                .transactionAware()
                //对于不同的cacheName我们可以设置不同的过期时间
                .withCacheConfiguration("app:",getCacheConfigurationWithTtl(Duration.ofHours(5)))
                .withCacheConfiguration("user:",getCacheConfigurationWithTtl(Duration.ofHours(2)))
                .build();
        return cacheManager;
    }
    //缓存的基本配置对象
    private   RedisCacheConfiguration getCacheConfigurationWithTtl(Duration duration) {
        return RedisCacheConfiguration
                .defaultCacheConfig()
                //设置key value的序列化方式
                // 设置key为String
                .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
                // 设置value 为自动转Json的Object
                .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer))
                // 不缓存null
                .disableCachingNullValues()
                // 设置缓存的过期时间
                .entryTtl(duration);
    }

    //操纵缓存的模板
    @Bean
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        System.out.println("redisTemplate");
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
        redisTemplate.setConnectionFactory(factory);
        return redisTemplate;
    }
    //操纵缓存的模板
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory factory) {
        System.out.println("stringTemplate");
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setKeySerializer(new StringRedisSerializer());
        stringRedisTemplate.setValueSerializer(fastJsonRedisSerializer);
        stringRedisTemplate.setConnectionFactory(factory);
        stringRedisTemplate.setHashValueSerializer(fastJsonRedisSerializer);
        return stringRedisTemplate;
    }

    //缓存的异常处理
    @Bean
    @Override
    public CacheErrorHandler errorHandler() {
        // 异常处理,当Redis发生异常时,打印日志,但是程序正常走
        log.info("初始化 -> [{}]", "Redis CacheErrorHandler");
        return new CacheErrorHandler() {
            @Override
            public void handleCacheGetError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheGetError:key -> [{}]", key, e);
            }

            @Override
            public void handleCachePutError(RuntimeException e, Cache cache, Object key, Object value) {
                log.error("Redis occur handleCachePutError:key -> [{}];value -> [{}]", key, value, e);
            }

            @Override
            public void handleCacheEvictError(RuntimeException e, Cache cache, Object key) {
                log.error("Redis occur handleCacheEvictError:key -> [{}]", key, e);
            }

            @Override
            public void handleCacheClearError(RuntimeException e, Cache cache) {
                log.error("Redis occur handleCacheClearError:", e);
            }
        };
    }
}


//重写FastJsonRedisSerialize的实现方式。
class FastJsonRedisSerializer<T> implements RedisSerializer<T> {
    private ObjectMapper objectMapper = new ObjectMapper();
    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static {
        // 全局开启AutoType,这里方便开发,使用全局的方式
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
        // 建议使用这种方式,小范围指定白名单
        // ParserConfig.getGlobalInstance().addAccept("me.zhengjie.domain");
        // key的序列化采用StringRedisSerializer
    }

    public FastJsonRedisSerializer(Class<T> clazz) {
        super();
        this.clazz = clazz;
    }

    //序列化 我们存储时,存储的是json对象,而默认存储的是byte类型的,所以在可视化窗口上显示时,看到的是乱码
    @Override
    public byte[] serialize(T t) throws SerializationException {
        System.out.println("进行序列化");
        if (t == null) {
            return new byte[0];
        }
        return JSON.toJSONString(t, SerializerFeature.WriteClassName).getBytes(DEFAULT_CHARSET);
    }

    //反序列化
    @Override
    public T deserialize(byte[] bytes) throws SerializationException {
        if (bytes == null || bytes.length <= 0) {
            return null;
        }
        String str = new String(bytes, DEFAULT_CHARSET);
        return JSON.parseObject(str, clazz);
    }
}


Redis工具类

spring虽然提供了template对redis的操作进行了封装,但是实际使用还是有些麻烦,实际项目中,我们通常使用redis的工具类来进行操作。
附:redis工具类

/**
 * 功能描述:SpringData Redis 的工具类
 *
 * @author 
 * Date: 2020/4/11 21:07
 **/
@RequiredArgsConstructor
@Component
@SuppressWarnings({"unchecked", "all"})
@Slf4j
public class RedisUtils {

    private final RedisTemplate<Object, Object> redisTemplate;

    // =============================common============================

    /**
     * 指定缓存失效时间
     *
     * @param key  键
     * @param time 时间(秒)
     */
    public boolean expire(String key, long time) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, TimeUnit.SECONDS);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * 根据 key 获取过期时间
     *
     * @param key 键 不能为null
     * @return 时间(秒) 返回0代表为永久有效
     */
    public long getExpire(Object key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 查找匹配key
     *
     * @param pattern key
     * @return /
     */
    public List<String> scan(String pattern) {
        ScanOptions options = ScanOptions.scanOptions().match(pattern).build();
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
        Cursor<byte[]> cursor = rc.scan(options);
        List<String> result = new ArrayList<>();
        while (cursor.hasNext()) {
            result.add(new String(cursor.next()));
        }
        try {
            RedisConnectionUtils.releaseConnection(rc, factory);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 分页查询 key
     *
     * @param patternKey key
     * @param page       页码
     * @param size       每页数目
     * @return /
     */
    public List<String> findKeysForPage(String patternKey, int page, int size) {
        ScanOptions options = ScanOptions.scanOptions().match(patternKey).build();
        RedisConnectionFactory factory = redisTemplate.getConnectionFactory();
        RedisConnection rc = Objects.requireNonNull(factory).getConnection();
        Cursor<byte[]> cursor = rc.scan(options);
        List<String> result = new ArrayList<>(size);
        int tmpIndex = 0;
        int fromIndex = page * size;
        int toIndex = page * size + size;
        while (cursor.hasNext()) {
            if (tmpIndex >= fromIndex && tmpIndex < toIndex) {
                result.add(new String(cursor.next()));
                tmpIndex++;
                continue;
            }
            // 获取到满足条件的数据后,就可以退出了
            if (tmpIndex >= toIndex) {
                break;
            }
            tmpIndex++;
            cursor.next();
        }
        try {
            RedisConnectionUtils.releaseConnection(rc, factory);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    /**
     * 判断key是否存在
     *
     * @param key 键
     * @return true 存在 false不存在
     */
    public boolean hasKey(String key) {
        try {
            return redisTemplate.hasKey(key);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除缓存
     *
     * @param key 可以传一个值 或多个
     */
    public void del(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(CollectionUtils.arrayToList(key));
            }
        }
    }

    // ============================String=============================

    /**
     * 普通缓存获取
     *
     * @param key 键
     * @return 值
     */
    public Object get(String key) {
        return key == null ? null : redisTemplate.opsForValue().get(key);
    }

    /**
     * 批量获取
     *
     * @param keys
     * @return
     */
    public List<Object> multiGet(List<String> keys) {
        Object obj = redisTemplate.opsForValue().multiGet(Collections.singleton(keys));
        return null;
    }

    /**
     * 普通缓存放入
     *
     * @param key   键
     * @param value 值
     * @return true成功 false失败
     */
    public boolean set(String key, Object value) {
        try {
            redisTemplate.opsForValue().set(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒) time要大于0 如果time小于等于0 将设置无限期
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, TimeUnit.SECONDS);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 普通缓存放入并设置时间
     *
     * @param key      键
     * @param value    值
     * @param time     时间
     * @param timeUnit 类型
     * @return true成功 false 失败
     */
    public boolean set(String key, Object value, long time, TimeUnit timeUnit) {
        try {
            if (time > 0) {
                redisTemplate.opsForValue().set(key, value, time, timeUnit);
            } else {
                set(key, value);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    // ================================Map=================================

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return 值
     */
    public Object hget(String key, String item) {
        return redisTemplate.opsForHash().get(key, item);
    }

    /**
     * 获取hashKey对应的所有键值
     *
     * @param key 键
     * @return 对应的多个键值
     */
    public Map<Object, Object> hmget(String key) {
        return redisTemplate.opsForHash().entries(key);

    }

    /**
     * HashSet
     *
     * @param key 键
     * @param map 对应多个键值
     * @return true 成功 false 失败
     */
    public boolean hmset(String key, Map<String, Object> map) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * HashSet 并设置时间
     *
     * @param key  键
     * @param map  对应多个键值
     * @param time 时间(秒)
     * @return true成功 false失败
     */
    public boolean hmset(String key, Map<String, Object> map, long time) {
        try {
            redisTemplate.opsForHash().putAll(key, map);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 向一张hash表中放入数据,如果不存在将创建
     *
     * @param key   键
     * @param item  项
     * @param value 值
     * @param time  时间(秒) 注意:如果已存在的hash表有时间,这里将会替换原有的时间
     * @return true 成功 false失败
     */
    public boolean hset(String key, String item, Object value, long time) {
        try {
            redisTemplate.opsForHash().put(key, item, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 删除hash表中的值
     *
     * @param key  键 不能为null
     * @param item 项 可以使多个 不能为null
     */
    public void hdel(String key, Object... item) {
        redisTemplate.opsForHash().delete(key, item);
    }

    /**
     * 判断hash表中是否有该项的值
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     * @return true 存在 false不存在
     */
    public boolean hHasKey(String key, String item) {
        return redisTemplate.opsForHash().hasKey(key, item);
    }

    /**
     * hash递增 如果不存在,就会创建一个 并把新增后的值返回
     *
     * @param key  键
     * @param item 项
     * @param by   要增加几(大于0)
     * @return
     */
    public double hincr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, by);
    }

    /**
     * hash递减
     *
     * @param key  键
     * @param item 项
     * @param by   要减少记(小于0)
     * @return
     */
    public double hdecr(String key, String item, double by) {
        return redisTemplate.opsForHash().increment(key, item, - by);
    }

    // ============================set=============================

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     * @return
     */
    public Set<Object> sGet(String key) {
        try {
            return redisTemplate.opsForSet().members(key);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据value从一个set中查询,是否存在
     *
     * @param key   键
     * @param value 值
     * @return true 存在 false不存在
     */
    public boolean sHasKey(String key, Object value) {
        try {
            return redisTemplate.opsForSet().isMember(key, value);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将数据放入set缓存
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSet(String key, Object... values) {
        try {
            return redisTemplate.opsForSet().add(key, values);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 将set数据放入缓存
     *
     * @param key    键
     * @param time   时间(秒)
     * @param values 值 可以是多个
     * @return 成功个数
     */
    public long sSetAndTime(String key, long time, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().add(key, values);
            if (time > 0) {
                expire(key, time);
            }
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 获取set缓存的长度
     *
     * @param key 键
     * @return
     */
    public long sGetSetSize(String key) {
        try {
            return redisTemplate.opsForSet().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 移除值为value的
     *
     * @param key    键
     * @param values 值 可以是多个
     * @return 移除的个数
     */
    public long setRemove(String key, Object... values) {
        try {
            Long count = redisTemplate.opsForSet().remove(key, values);
            return count;
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    // ===============================增 list @ 2020/2/6=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     * @return
     */
    public List<Object> lGet(String key, long start, long end) {
        try {
            return redisTemplate.opsForList().range(key, start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 获取list缓存的长度
     *
     * @param key 键
     * @return
     */
    public long lGetListSize(String key) {
        try {
            return redisTemplate.opsForList().size(key);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    /**
     * 通过索引 获取list中的值
     *
     * @param key   键
     * @param index 索引 index>=0时, 0 表头,1 第二个元素,依次类推;index<0时,-1,表尾,-2倒数第二个元素,依次类推
     * @return
     */
    public Object lGetIndex(String key, long index) {
        try {
            return redisTemplate.opsForList().index(key, index);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, Object value) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, Object value, long time) {
        try {
            redisTemplate.opsForList().rightPush(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @return
     */
    public boolean lSet(String key, List<Object> value) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 将list放入缓存
     *
     * @param key   键
     * @param value 值
     * @param time  时间(秒)
     * @return
     */
    public boolean lSet(String key, List<Object> value, long time) {
        try {
            redisTemplate.opsForList().rightPushAll(key, value);
            if (time > 0) {
                expire(key, time);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 根据索引修改list中的某条数据
     *
     * @param key   键
     * @param index 索引
     * @param value 值
     * @return /
     */
    public boolean lUpdateIndex(String key, long index, Object value) {
        try {
            redisTemplate.opsForList().set(key, index, value);
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * 移除N个值为value
     *
     * @param key   键
     * @param count 移除多少个
     * @param value 值
     * @return 移除的个数
     */
    public long lRemove(String key, long count, Object value) {
        try {
            return redisTemplate.opsForList().remove(key, count, value);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    //-----------------------自定义工具扩展 @ 2020/2/6----------------------

    /**
     * 功能描述:在list的右边添加元素
     * 如果键不存在,则在执行推送操作之前将其创建为空列表
     *
     * @param key 键
     * @return value 值
     * @author 
     * Date: 2020/2/6 23:22
     */
    public Long rightPushValue(String key, Object value) {
        return redisTemplate.opsForList().rightPush(key, value);
    }

    /**
     * 功能描述:在list的右边添加集合元素
     * 如果键不存在,则在执行推送操作之前将其创建为空列表
     *
     * @param key 键
     * @return value 值
     * @author 
     * Date: 2020/2/6 23:22
     */
    public Long rightPushList(String key, List<Object> values) {
        return redisTemplate.opsForList().rightPushAll(key, values);
    }

    /**
     * 指定缓存失效时间,携带失效时间的类型
     *
     * @param key  键
     * @param time 时间(秒)
     * @param unit 时间的类型  TimeUnit枚举
     */
    public boolean expire(String key, long time, TimeUnit unit) {
        try {
            if (time > 0) {
                redisTemplate.expire(key, time, unit);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    /**
     * @param prefix 前缀
     * @param ids    id
     */
    public void delByKeys(String prefix, Set<Long> ids) {
        Set<Object> keys = new HashSet<>();
        for (Long id : ids) {
            keys.addAll(redisTemplate.keys(new StringBuffer(prefix).append(id).toString()));
        }
        long count = redisTemplate.delete(keys);
        // 此处提示可自行删除
        log.debug("--------------------------------------------");
        log.debug("成功删除缓存:" + keys.toString());
        log.debug("缓存删除数量:" + count + "个");
        log.debug("--------------------------------------------");
    }

}

参考Blog

spring cache
spring cache 缓存注解的使用
SpringBoot + Redis:基本配置及使用
SpringBoot配置多CacheManager
Spring缓存源码剖析:(二)CacheManager
Redis使用FastJson序列化/FastJson2JsonRedisSerializer
SpringBoot下Redis相关配置是如何被初始化的(细看)

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐