springboot整合redis做简单缓存

Posted 全他吗被取了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了springboot整合redis做简单缓存相关的知识,希望对你有一定的参考价值。

一、引入依赖

<!-- 引入redis依赖-->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--使用fastjson做序列化和反序列化-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.75</version>
</dependency>

二、配置redis连接

spring:
  redis:
    host: localhost
    port: 6379

三、编写redis常用工具类(网上很多,随便找一个就行)


@Component
public final class RedisUtil 

    @Autowired
    private 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);
            
            return true;
         catch (Exception e) 
            e.printStackTrace();
            return false;
        
    

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


    /**
     * 判断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 可以传一个值 或多个
     */
    @SuppressWarnings("unchecked")
    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 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 delta 要增加几(大于0)
     */
    public long incr(String key, long delta) 
        if (delta < 0) 
            throw new RuntimeException("递增因子必须大于0");
        
        return redisTemplate.opsForValue().increment(key, delta);
    


    /**
     * 递减
     *
     * @param key   键
     * @param delta 要减少几(小于0)
     */
    public long decr(String key, long delta) 
        if (delta < 0) 
            throw new RuntimeException("递减因子必须大于0");
        
        return redisTemplate.opsForValue().increment(key, -delta);
    


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

    /**
     * HashGet
     *
     * @param key  键 不能为null
     * @param item 项 不能为null
     */
    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 对应多个键值
     */
    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)
     */
    public double hincr(String key, String item, double by) 
        return redisTemplate.opsForHash().increment(key, item, by);
    


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


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

    /**
     * 根据key获取Set中的所有值
     *
     * @param key 键
     */
    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 键
     */
    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=================================

    /**
     * 获取list缓存的内容
     *
     * @param key   键
     * @param start 开始
     * @param end   结束 0 到 -1代表所有值
     */
    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 键
     */
    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倒数第二个元素,依次类推
     */
    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 值
     */
    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  时间(秒)
     */
    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 
            Long remove = redisTemplate.opsForList().remove(key, count, value);
            return remove;
         catch (Exception e) 
            e.printStackTrace();
            return 0;
        

    


四、redis用fastjson来序列化和反序列化


public class FastJsonJsonRedisSerializer<T> implements RedisSerializer<T>

    public static final Charset DEFAULT_CHARSET = Charset.forName("UTF-8");

    private Class<T> clazz;

    static 
        ParserConfig.getGlobalInstance().setAutoTypeSupport(true);
    

    public FastJsonJsonRedisSerializer(Class<T> clazz)
    
        super();
        this.clazz = clazz;
    

    @Override
    public byte[] serialize(T t) throws SerializationException
    
        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);
    
    protected JavaType getJavaType(Class<T> clazz)
        return TypeFactory.defaultInstance().constructType(clazz);
    

五、编写redis配置类,并用fastjson来序列化和反序列化


@Configuration
public class RedisConfig 

    @Bean
    public RedisTemplate<Object,Object> redisTemplate(RedisConnectionFactory factory)
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(factory);
        FastJsonJsonRedisSerializer jsonJsonRedisSerializer = new FastJsonJsonRedisSerializer<>(Object.class);
        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        StringRedisSerializer serializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(serializer);
        redisTemplate.setValueSerializer(jsonJsonRedisSerializer);
        //hash的value也采用jackson序列化方式
        redisTemplate.setHashKeySerializer(serializer);
        redisTemplate.setHashValueSerializer(jsonJsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    

六、连接测试

    @Test
    public void test3()
        Long id = 4L;
        SysUser user = userService.selectUserByUsername(id);
        redisUtil.set(user.getUsername(),user);
        SysUser u = (SysUser) redisUtil.get(user.getUsername());
        System.out.println("从redis缓存中查询结果为:"+u);
    

springboot整合redis缓存一些知识点

前言

最近在做智能家居平台,考虑到家居的控制需要快速的响应于是打算使用redis缓存。一方面减少数据库压力另一方面又能提高响应速度。项目中使用的技术栈基本上都是大家熟悉的springboot全家桶,在springboot2.x以后操作redis的客户端推荐使用lettuce(生菜)取代jedis。

jedis的劣势主要在于直连redis,又无法做到弹性收缩。

一、配置文件

application.yml文件中的内容

spring:
  application:
    name: simple-lettuce
  cache:
    type: redis
    redis:
      # 缓存超时时间ms
      time-to-live: 60000
      # 是否缓存空值
      cache-null-values: true
  redis:
    host: 127.0.0.1
    port: 6379
    password: 123456
    # 连接超时时间(毫秒)
    timeout: 60000
    # Redis默认情况下有16个分片,这里配置具体使用的分片,默认是0
    database: 1
    # spring2.x redis client 采用了lettuce(生菜),放弃使用jedis
    lettuce:
      # 关闭超时时间
      shutdown-timeout: 30000
      pool:
        # 连接池最大连接数(使用负值表示没有限制) 默认 8
        max-active: 30
        # 连接池最大阻塞等待时间(使用负值表示没有限制) 默认 -1
        max-wait: -1
        # 连接池中的最大空闲连接 默认 8
        max-idle: 8
        # 连接池中的最小空闲连接 默认 0
        min-idle: 0

说明:

  • spring.cache.type: redis

已经表明使用项目采用redis做为缓存方式。

  • spring.cache.redis.cache-null-values: true

表示是否缓存空值,一般情况下是允许的。因为这涉及到缓存的三大问题:缓存穿透、缓存雪崩、缓存击穿。

如果设置false即不允许缓存空值,这样会导致很多请求数据库没有的数据时,不会缓存到redis导致每次都会请求到数据库。这种情况即:缓存穿透。

具体想初步了解这些概念可以参考文章:缓存三大问题及解决方案!

二、config配置类


@Configuration
@EnableCaching
public class RedisTemplateConfig extends CachingConfigurerSupport {

    private static Map<String, RedisCacheConfiguration> cacheMap = Maps.newHashMap();

    @Bean(name = "stringRedisTemplate")
    @ConditionalOnMissingBean(name = "stringRedisTemplate") //表示:如果容器已经有redisTemplate bean就不再注入
    public StringRedisTemplate stringRedisTemplate(LettuceConnectionFactory redisConnectionFactory) {return new StringRedisTemplate(redisConnectionFactory);
    }

    @Bean(name = "redisTemplate")
    @ConditionalOnMissingBean(name = "redisTemplate")
    public RedisTemplate<String, Object> redisTemplate(LettuceConnectionFactory lettuceConnectionFactory) {
        System.out.println("RedisTemplateConfig.RedisTemplate");
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // key的序列化采用StringRedisSerializer
        template.setKeySerializer(keySerializer());
        template.setHashKeySerializer(keySerializer());
        // value值的序列化采用fastJsonRedisSerializer
        template.setValueSerializer(valueSerializer()); //使用fastjson序列化
        template.setHashValueSerializer(valueSerializer()); //使用fastjson序列化
        template.setConnectionFactory(lettuceConnectionFactory);
        return template;
    }

    /**
     * 添加自定义缓存异常处理
     * 当缓存读写异常时,忽略异常
     * 参考:https://blog.csdn.net/sz85850597/article/details/89301331
     */
    @Override
    public CacheErrorHandler errorHandler() {
        return new IgnoreCacheErrorHandler();
    }

    @SuppressWarnings("Duplicates")
    @Bean
    @Primary//当有多个管理器的时候,必须使用该注解在一个管理器上注释:表示该管理器为默认的管理器
    public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
        // 默认配置
        RedisCacheConfiguration defaultCacheConfig = RedisCacheConfiguration.defaultCacheConfig()
                .serializeKeysWith(keyPair())
                .serializeValuesWith(valuePair())
                .entryTtl(Duration.ofSeconds(DEFAULT_TTL_SECS)) //设置过期时间
                .disableCachingNullValues();

        // 其它配置
        for(MyCaches cache : MyCaches.values()) {
            cacheMap.put(cache.name(),
                    RedisCacheConfiguration.defaultCacheConfig()
                            .serializeKeysWith(keyPair())
                            .serializeValuesWith(valuePair())
                            .entryTtl(cache.getTtl())
                            // .disableCachingNullValues() // 表示不允许缓存空值
                            .disableKeyPrefix() // 不使用默认前缀
                    // .prefixKeysWith("mytest") // 添加自定义前缀
            );
        }

        /** 遍历MyCaches添加缓存配置*/
        RedisCacheManager cacheManager = RedisCacheManager.builder(
                RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory)
        )
                .cacheDefaults(defaultCacheConfig)
                .withInitialCacheConfigurations(cacheMap)
                .transactionAware()
                .build();

        ParserConfig.getGlobalInstance().addAccept("mypackage.db.entity.");
        return cacheManager;
    }

    /**
     * key序列化方式
     * @return
     */
    private RedisSerializationContext.SerializationPair<String> keyPair() {
        RedisSerializationContext.SerializationPair<String> keyPair =
                RedisSerializationContext.SerializationPair.fromSerializer(keySerializer());
        return keyPair;
    }

    private RedisSerializer<String> keySerializer() {
        return new StringRedisSerializer();
    }

    /**
     * value序列化方式
     * @return
     */
    private RedisSerializationContext.SerializationPair<Object> valuePair() {
        RedisSerializationContext.SerializationPair<Object> valuePair =
                RedisSerializationContext.SerializationPair.fromSerializer(valueSerializer());
        return valuePair;
    }

    /**
     * 使用fastjson序列化
     * @return
     */
    private RedisSerializer<Object> valueSerializer() {
        MyFastJsonRedisSerializer<Object> fastJsonRedisSerializer = new MyFastJsonRedisSerializer<>(Object.class);
        return fastJsonRedisSerializer;
    }

    @Getter
    private enum MyCaches {
        defaultCache(Duration.ofDays(1)),
        MyCaches(Duration.ofMinutes(10));

        MyCaches(Duration ttl) {
            this.ttl = ttl;
        }
        /** 失效时间 */
        private Duration ttl = Duration.ofHours(1);
    }
}

 说明

1. 类上的注解@EnableCaching

表明开启缓存功能。

2. extends CachingConfigurerSupport

这个类就很丰富了,其实如果没有什么特别操作也可以不用继承这个类。

这个类可以支持动态选择缓存方式,比如项目中不止一种缓存方案,有可能有ehcache那么可以自定义在什么情况下使用redis使用情况下使用ehcache。还有一些有关异常的处理。我也不是很懂具体可以参考:

3. StringRedisTemplate和RedisTemplate的使用

(1)两者的主要差别是:如果你只想缓存简单的字符串选择StringRedisTemplate是一个明智的举措。如果想使用redis缓存一些对象数据肯定是要选择RedisTemplate。
 
(2)RedisTemplate需要注意一点就是要怎么选择序列化工具。默认使用jdk的序列化缓存数据后即value值是无法直接阅读的而存的二进制数据。
通常我们会选择jackson或者fastjson来序列化对象,把对象转换成json格式。两者序列化对象后都会在头部加上一个对象类路径如:@type com.mypackage.entity.User。这个也算是一种安全策略。
比如使用fastjosn就会在cacheManager中指定序列化对象的包所在位置白名单:ParserConfig.getGlobalInstance().addAccept("mypackage.db.entity.");
fastjson官方说明:https://github.com/alibaba/fastjson/wiki/enable_autotype
 
(3)还有需要注意如果value是string类型。RedisTemplate会在字符串外围再加一对双引号,如""abc""。如果使用RedisTemplate读取则能得到abc,但是我在项目使用Jedis读取就成了"abc"这就导致这些字符串无法被反序列化。
 
(4)StringRedisTemplate和RedisTemplate两者数据是相互隔离的,如果使用StringRedisTemplate存入的数据使用RedisTemplate是无法读取、删除的。
 
 

三、缓存注解使用

@Cacheable 使用在查询方法上

@CachePut 使用在更新、保存方法上

@CacheEvict 使用在删除方法上

需要注意的是@Cacheable@CachePut方法一定要有返回被缓存对象。因为注解使用的AOP切面如果没有返回值表示缓存对象为空值。

@CacheConfig注解在类上,可以选择使用哪个缓存、缓存管理器、Key生成器

 

好了以上就是最近在项目中的一些知识点总结,如果以后使用缓存有新的体会我会同步更新的。

以上是关于springboot整合redis做简单缓存的主要内容,如果未能解决你的问题,请参考以下文章

SpringBoot 整合 Redis缓存

Springboot2.x+shiro+redis整合填坑 redis只做缓存的情况

SpringBoot整合redis缓存

springboot整合redis缓存一些知识点

springboot中使用RedisTemplate实现redis数据缓存

springboot 整合redis 以及redis的简单使用