当前位置:   article > 正文

Spring Cache整合Redis_spring redis cacheevict allentries

spring redis cacheevict allentries

文章介绍

继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 "";
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

举例:

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;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
@Repository
@CacheConfig(cacheNames = "appDao")
public interface AppDAO extends BaseMapper<App> {
    List<App> findAll();
    App findOne(Long id);
    void deleteOne(Long id);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7

效果:
在这里插入图片描述从上面的运行结果我们可以看出,虽然我们在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)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

value/cacheNames:

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

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

执行语句

    public void testRedis(){
        appService.findOne(8L);
        appService.findOne(9L);
    }
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述

unless:

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

    public void testRedis(){
        appService.findOne(8L);
        appService.findOne(9L);
    }
  • 1
  • 2
  • 3
  • 4

在这里插入图片描述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,则不进行缓存
  • 1

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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
@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()));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

在这里插入图片描述

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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

测试

	@Test
    public void testRedis(){
        appService.findOne(8L);
        appService.findOne(9L);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

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

 @Test
    public void  testDelete(){
      //删除
      appService.deleteOne(8L);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

在这里插入图片描述
虽然我们删除的是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) {
    ....
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
在这里插入代码片
  • 1

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
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

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

默认情况下,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
  • 2
  • 3
  • 4
  • 5
  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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  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()));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在这里插入图片描述

总结

通过原生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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
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
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32

配置方式二:
使用原有的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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

默认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();
            }
        };
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
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);
            }
        };
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
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;
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
7.使用FastJson解决缓存的乱码问题

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

        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.70</version>
        </dependency>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
//重写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);
    }
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
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()));
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

在这里插入图片描述

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

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

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

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

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 
  • 1
  • 2
  • 3

这是我们查出来的信息。
在这里插入图片描述
我们往缓存中存储时,以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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

测试:

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

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

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

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

分析:

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

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

  @Override
    @CacheEvict(key = "'id:'+#id")
    public void deleteOne(Long id) {
   userDAO.deleteOne(id);
    }
  • 1
  • 2
  • 3
  • 4
  • 5

删除/更新部门:通过逻辑获取该部门中的所有人员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);
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

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

    @Test
    public  void  testUserAndDept(){

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

    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

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

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

附:

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);
    }
}


  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
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("--------------------------------------------");
    }

}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • 378
  • 379
  • 380
  • 381
  • 382
  • 383
  • 384
  • 385
  • 386
  • 387
  • 388
  • 389
  • 390
  • 391
  • 392
  • 393
  • 394
  • 395
  • 396
  • 397
  • 398
  • 399
  • 400
  • 401
  • 402
  • 403
  • 404
  • 405
  • 406
  • 407
  • 408
  • 409
  • 410
  • 411
  • 412
  • 413
  • 414
  • 415
  • 416
  • 417
  • 418
  • 419
  • 420
  • 421
  • 422
  • 423
  • 424
  • 425
  • 426
  • 427
  • 428
  • 429
  • 430
  • 431
  • 432
  • 433
  • 434
  • 435
  • 436
  • 437
  • 438
  • 439
  • 440
  • 441
  • 442
  • 443
  • 444
  • 445
  • 446
  • 447
  • 448
  • 449
  • 450
  • 451
  • 452
  • 453
  • 454
  • 455
  • 456
  • 457
  • 458
  • 459
  • 460
  • 461
  • 462
  • 463
  • 464
  • 465
  • 466
  • 467
  • 468
  • 469
  • 470
  • 471
  • 472
  • 473
  • 474
  • 475
  • 476
  • 477
  • 478
  • 479
  • 480
  • 481
  • 482
  • 483
  • 484
  • 485
  • 486
  • 487
  • 488
  • 489
  • 490
  • 491
  • 492
  • 493
  • 494
  • 495
  • 496
  • 497
  • 498
  • 499
  • 500
  • 501
  • 502
  • 503
  • 504
  • 505
  • 506
  • 507
  • 508
  • 509
  • 510
  • 511
  • 512
  • 513
  • 514
  • 515
  • 516
  • 517
  • 518
  • 519
  • 520
  • 521
  • 522
  • 523
  • 524
  • 525
  • 526
  • 527
  • 528
  • 529
  • 530
  • 531
  • 532
  • 533
  • 534
  • 535
  • 536
  • 537
  • 538
  • 539
  • 540
  • 541
  • 542
  • 543
  • 544
  • 545
  • 546
  • 547
  • 548
  • 549
  • 550
  • 551
  • 552
  • 553
  • 554
  • 555
  • 556
  • 557
  • 558
  • 559
  • 560
  • 561
  • 562
  • 563
  • 564
  • 565
  • 566
  • 567
  • 568
  • 569
  • 570
  • 571
  • 572
  • 573
  • 574
  • 575
  • 576
  • 577
  • 578
  • 579
  • 580
  • 581
  • 582
  • 583
  • 584
  • 585
  • 586
  • 587
  • 588
  • 589
  • 590
  • 591
  • 592
  • 593
  • 594
  • 595
  • 596
  • 597
  • 598
  • 599
  • 600
  • 601
  • 602
  • 603
  • 604
  • 605
  • 606
  • 607
  • 608
  • 609
  • 610
  • 611
  • 612
  • 613
  • 614
  • 615
  • 616
  • 617
  • 618
  • 619
  • 620
  • 621
  • 622
  • 623
  • 624
  • 625
  • 626
  • 627
  • 628
  • 629
  • 630
  • 631
  • 632
  • 633
  • 634
  • 635
  • 636
  • 637
  • 638
  • 639
  • 640
  • 641
  • 642
  • 643
  • 644
  • 645
  • 646
  • 647
  • 648
  • 649
  • 650
  • 651
  • 652
  • 653
  • 654
  • 655
  • 656
  • 657
  • 658
  • 659
  • 660
  • 661
  • 662
  • 663
  • 664
  • 665
  • 666
  • 667
  • 668
  • 669
  • 670
  • 671
  • 672
  • 673
  • 674
  • 675
  • 676
  • 677
  • 678
  • 679
  • 680
  • 681
  • 682
  • 683
  • 684
  • 685
  • 686
  • 687
  • 688
  • 689
  • 690
  • 691
  • 692
  • 693
  • 694
  • 695
  • 696

参考Blog

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/爱喝兽奶帝天荒/article/detail/744999
推荐阅读
相关标签
  

闽ICP备14008679号