第十章:springboot2 配置Redis和緩存

springboot版本:2.7.6

集成redis

1. 添加依賴(lài)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 單節(jié)點(diǎn)設(shè)置啟用連接池時(shí)添加 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>

2. 添加配置

spring:
  redis:
    database: 0
    password: yx123123=
    host: 192.168.110.108
    port: 6379
    client-type: lettuce
    lettuce:
      pool:
    # 連接耗盡時(shí)挨决,最多等待10秒
        max-wait: 10s
    # 連接池中最小空閑連接數(shù)
        min-idle: 4
    # 空閑對(duì)象逐出器運(yùn)行間隔時(shí)長(zhǎng)
        time-between-eviction-runs: 10s
    # 啟用池
        enabled: true

3. Java Configuration

/** 
 * redis 連接工廠
 */
 @Autowired
private RedisConnectionFactory connectionFactory;

/**
 * 實(shí)例化RedisTemplate,使用Json序列化弓颈,并對(duì)日期/時(shí)間進(jìn)行格式化,統(tǒng)一使用字符串key
 *
 * @return RedisTemplate
 */
@Bean
public RedisTemplate<String, Object> redisTemplate() {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    ObjectMapper objectMapper = new ObjectMapper();
    // 日期格式支持
    JavaTimeModule timeModule = new JavaTimeModule();
    final ZoneId DEFAULT_ZONE_ID = ZoneId.of("CTT", SHORT_IDS);
    final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(DEFAULT_ZONE_ID);
    final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(DEFAULT_ZONE_ID);
    timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER)).addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER)).addSerializer(LocalDate.class, new LocalDateSerializer(DATE_FORMATTER)).addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATE_TIME_FORMATTER));
    objectMapper.registerModule(timeModule);
    # 反序列化時(shí)忽略對(duì)象中不存在的屬性
    objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
    objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    # 反序列化時(shí)可以轉(zhuǎn)為對(duì)象
    objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
    GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
    // key統(tǒng)一使用字符串
    template.setKeySerializer(RedisSerializer.string());
    template.setHashKeySerializer(RedisSerializer.string());
    // value統(tǒng)一使用json
    template.setValueSerializer(genericJackson2JsonRedisSerializer);
    template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
    template.setConnectionFactory(connectionFactory);
    return template;
}

集成cache

1. 添加依賴(lài)

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2. 添加配置

spring:  
  cache:
    type: REDIS
    redis:
      # 配置緩存時(shí)長(zhǎng)15分鐘
      time-to-live: 15m
      cache-null-values: false
      enable-statistics: true
      # 緩存key的前綴
      key-prefix: 'cache:${spring.application.name}:'

3. 代碼

3.1 Java Configuration

@EnableCaching
@Configuration
@EnableConfigurationProperties(CacheProperties.class)
public class RedisCacheAutoConfiguration extends CachingConfigurerSupport {
    /**
     * redis 連接工廠
     */
    private final RedisConnectionFactory connectionFactory;

    /**
     * 緩存配置數(shù)據(jù)
     */
    private final CacheProperties cacheProperties;

    public RedisCacheAutoConfiguration(RedisConnectionFactory connectionFactory, CacheProperties cacheProperties) {
        this.connectionFactory = connectionFactory;
        this.cacheProperties = cacheProperties;
    }


    @Bean
    @Override
    public CacheManager cacheManager() {
        CacheProperties.Redis redisCacheProperties = cacheProperties.getRedis();
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(connectionFactory).cacheDefaults(redisCacheConfiguration(redisCacheProperties)).transactionAware().cacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(1000)));
        if (redisCacheProperties.isEnableStatistics()) {
            builder.enableStatistics();
        }
        return builder.build();
    }

    /**
     * 自定義KeyGenerator
     * key結(jié)構(gòu) key-prefix+cacheName::類(lèi)名.方法名(參數(shù))
     * 最終生成的key會(huì)受到key-prefix和@Cacheable注解中配置的cacheNames影響 示例:
     * <b>@Cacheable(cacheNames = {"query.hello"})</b>
     * query.hello::HelloController.hello(spring)
     * 如果配置文件中配置了key-prefix: 'cache:'
     * cache:query.hello::HelloController.hello(world)
     *
     * @return KeyGenerator
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            String paramStr = Stream.of(params).map(Object::toString).collect(Collectors.joining(",", "(", ")"));
            return target.getClass().getSimpleName() + "." + method.getName() + paramStr;
        };
    }

    /**
     * redis cache configuration 配置
     * 如果需要緩存LocalDateTime類(lèi)型穴张,自定義value序列化器
     *
     * @param redisCacheProperties redis緩存配置
     * @return RedisCacheConfiguration
     */
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties.Redis redisCacheProperties) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new RedisCacheValueSerializer()));
        if (redisCacheProperties.getTimeToLive() != null) {
            cacheConfig = cacheConfig.entryTtl(redisCacheProperties.getTimeToLive());
        }
        if (redisCacheProperties.getKeyPrefix() != null) {
            cacheConfig = cacheConfig.prefixCacheNameWith(redisCacheProperties.getKeyPrefix());
        }
        if (!redisCacheProperties.isCacheNullValues()) {
            cacheConfig = cacheConfig.disableCachingNullValues();
        }
        if (!redisCacheProperties.isUseKeyPrefix()) {
            cacheConfig = cacheConfig.disableKeyPrefix();
        }
        // 這里會(huì)影響最終生成的緩存key
        if (StringUtils.hasText(redisCacheProperties.getKeyPrefix())) {
            cacheConfig = cacheConfig.prefixCacheNameWith(redisCacheProperties.getKeyPrefix());
        }
        return cacheConfig;
    }
}

3.2 自定義value序列化器

/**
 * spring cache redis 序列化器
 * 用redis緩存spring cache時(shí)使用前弯,解決了LocalDateTime序列化和反序列化問(wèn)題
 *
 * @author co
 * @since 2023-12-05 16:37:00
 */
public class RedisCacheValueSerializer extends GenericJackson2JsonRedisSerializer {
    public RedisCacheValueSerializer() {
        super();
    }

    @Override
    public Object deserialize(byte[] source) throws SerializationException {
        Object value = super.deserialize(source);
        if (value instanceof String) {
            String str = value == null ? "" : (String) value;
            // 以下為L(zhǎng)ocalDate和LocalDateTime的判斷和轉(zhuǎn)換,可以替換為其他更具效率的方式
            try {
                if (str.length() == 10) {
                    return LocalDate.parse(str, DATE_FORMATTER);
                }
                if (str.length() == 19) {
                    return LocalDateTime.parse(str, DATE_TIME_FORMATTER);
                }
            } catch (DateTimeParseException ex) {

            }
        }
        return value;
    }

    @Override
    public byte[] serialize(Object source) throws SerializationException {
        if (source instanceof LocalDateTime) {
            return super.serialize(((LocalDateTime) source).format(DATE_TIME_FORMATTER));
        }

        if (source instanceof LocalDate) {
            return super.serialize(((LocalDate) source).format(DATE_FORMATTER));
        }
        return super.serialize(source);
    }
}

完整示例

1. pom.xml

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- 單節(jié)點(diǎn)設(shè)置啟用連接池時(shí)添加 -->
<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
</dependency>
<!-- spring cache -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-cache</artifactId>
</dependency>

2. yaml

spring:
  application:
    name: app-name
  cache:
    type: REDIS
    redis:
      time-to-live: 15m
      cache-null-values: false
      enable-statistics: true
      key-prefix: 'cache:${spring.application.name}:'
  redis:
    database: 0
    password: yx123123=
    host: 192.168.110.108
    client-type: lettuce
    lettuce:
      pool:
        max-wait: 10s
        min-idle: 4
        time-between-eviction-runs: 10s
        enabled: true

3. Java代碼

3.1 config

/**
 * Redis & Cache相關(guān)配置類(lèi)
 * 配置了自定義KeyGenerator砰蠢,因此必須繼承CachingConfigurerSupport辨泳,否則不生效
 *
 * @author co
 * @since 2023-12-05 10:19:48
 */
@EnableCaching
@Configuration
@Import({RedisClient.class})
@EnableConfigurationProperties(CacheProperties.class)
public class RedisCacheAutoConfiguration extends CachingConfigurerSupport {
    /**
     * redis 連接工廠
     */
    private final RedisConnectionFactory connectionFactory;

    /**
     * 緩存配置數(shù)據(jù)
     */
    private final CacheProperties cacheProperties;

    public RedisCacheAutoConfiguration(RedisConnectionFactory connectionFactory, CacheProperties cacheProperties) {
        this.connectionFactory = connectionFactory;
        this.cacheProperties = cacheProperties;
    }

    /**
     * 實(shí)例化RedisTemplate虱岂,使用Json序列化,并對(duì)日期/時(shí)間進(jìn)行格式化菠红,統(tǒng)一使用字符串key
     *
     * @return RedisTemplate
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        ObjectMapper objectMapper = new ObjectMapper();
        // 日期格式支持
        final ZoneId DEFAULT_ZONE_ID = ZoneId.of("CTT", SHORT_IDS);
        final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd").withZone(DEFAULT_ZONE_ID);
        final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").withZone(DEFAULT_ZONE_ID);
        JavaTimeModule timeModule = new JavaTimeModule();
        timeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(DATE_FORMATTER)).addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(DATE_TIME_FORMATTER)).addSerializer(LocalDate.class, new LocalDateSerializer(DATE_FORMATTER)).addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(DATE_TIME_FORMATTER));
        objectMapper.registerModule(timeModule);
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.PROPERTY);
        GenericJackson2JsonRedisSerializer genericJackson2JsonRedisSerializer = new GenericJackson2JsonRedisSerializer(objectMapper);
        // key統(tǒng)一使用字符串
        template.setKeySerializer(RedisSerializer.string());
        template.setHashKeySerializer(RedisSerializer.string());
        // value統(tǒng)一使用json
        template.setValueSerializer(genericJackson2JsonRedisSerializer);
        template.setHashValueSerializer(genericJackson2JsonRedisSerializer);
        template.setConnectionFactory(connectionFactory);
        return template;
    }

    @Bean
    @Override
    public CacheManager cacheManager() {
        CacheProperties.Redis redisCacheProperties = cacheProperties.getRedis();
        RedisCacheManager.RedisCacheManagerBuilder builder = RedisCacheManager.builder(connectionFactory).cacheDefaults(redisCacheConfiguration(redisCacheProperties)).transactionAware().cacheWriter(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(1000)));
        if (redisCacheProperties.isEnableStatistics()) {
            builder.enableStatistics();
        }
        return builder.build();
    }

    /**
     * 自定義KeyGenerator
     * key結(jié)構(gòu) key-prefix+cacheName::類(lèi)名.方法名(參數(shù))
     * 最終生成的key會(huì)受到key-prefix和@Cacheable注解中配置的cacheNames影響 示例:
     * <b>@Cacheable(cacheNames = {"query.hello"})</b>
     * query.hello::HelloController.hello(spring)
     * 如果配置文件中配置了key-prefix: 'cache:'
     * cache:query.hello::HelloController.hello(world)
     *
     * @return KeyGenerator
     */
    @Bean
    @Override
    public KeyGenerator keyGenerator() {
        return (target, method, params) -> {
            String paramStr = Stream.of(params).map(Object::toString).collect(Collectors.joining(",", "(", ")"));
            return target.getClass().getSimpleName() + "." + method.getName() + paramStr;
        };
    }

    /**
     * redis cache configuration 配置
     * 如果需要緩存LocalDateTime等類(lèi)型第岖,需要在pom.xml中引入以下依賴(lài),可根據(jù)錯(cuò)誤提示進(jìn)行配置
     * <dependency>
     * <groupId>com.fasterxml.jackson.datatype</groupId>
     * <artifactId>jackson-datatype-jsr310</artifactId>
     * </dependency>
     *
     * @param redisCacheProperties redis緩存配置
     * @return RedisCacheConfiguration
     */
    RedisCacheConfiguration redisCacheConfiguration(CacheProperties.Redis redisCacheProperties) {
        RedisCacheConfiguration cacheConfig = RedisCacheConfiguration.defaultCacheConfig().serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new RedisCacheValueSerializer()));
        if (redisCacheProperties.getTimeToLive() != null) {
            cacheConfig = cacheConfig.entryTtl(redisCacheProperties.getTimeToLive());
        }
        if (redisCacheProperties.getKeyPrefix() != null) {
            cacheConfig = cacheConfig.prefixCacheNameWith(redisCacheProperties.getKeyPrefix());
        }
        if (!redisCacheProperties.isCacheNullValues()) {
            cacheConfig = cacheConfig.disableCachingNullValues();
        }
        if (!redisCacheProperties.isUseKeyPrefix()) {
            cacheConfig = cacheConfig.disableKeyPrefix();
        }
        // 這里會(huì)影響最終生成的緩存key
        if (StringUtils.hasText(redisCacheProperties.getKeyPrefix())) {
            cacheConfig = cacheConfig.prefixCacheNameWith(redisCacheProperties.getKeyPrefix());
        }
        return cacheConfig;
    }
}

3.2 redis工具類(lèi)

/**
 * Redis工具類(lèi)
 * <p>
 * 包含常用操作及獲取鎖
 *
 * @author co 
 * @since 2023-12-07 10:05:20
 */
public final class RedisClient {
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 指定緩存失效時(shí)間
     * (命令EXPIRE)
     *
     * @param key  鍵
     * @param time 時(shí)間(秒)
     * @return true / false
     */
    public Boolean expire(String key, int time) {
        if (time > 0) {
            return redisTemplate.expire(key, time, TimeUnit.SECONDS);
        }
        return false;
    }

    /**
     * 指定緩存失效時(shí)間
     * (命令EXPIRE)
     *
     * @param key      鍵
     * @param time     時(shí)間(秒)
     * @param timeUnit 時(shí)間單位
     * @return true / false
     */
    public Boolean expire(String key, long time, TimeUnit timeUnit) {
        if (time > 0) {
            return redisTemplate.expire(key, time, timeUnit);
        }
        return false;
    }

    /**
     * 根據(jù) key 獲取過(guò)期時(shí)間
     * (命令TTL)
     *
     * @param key 鍵
     * @return 秒
     */
    public Long getExpire(String key) {
        return redisTemplate.getExpire(key, TimeUnit.SECONDS);
    }

    /**
     * 判斷 key 是否存在
     * (命令EXISTS)
     *
     * @param key 鍵
     * @return true / false
     */
    public Boolean exists(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 刪除緩存
     * 不存在key不會(huì)拋異常
     *
     * @param keys 集合
     */
    public void delete(Collection<String> keys) {
        redisTemplate.delete(keys);
    }


    /**
     * 刪除緩存
     * (命令DEL)
     *
     * @param key 鍵(一個(gè)或者多個(gè))
     */
    public void delete(String... key) {
        if (key != null && key.length > 0) {
            if (key.length == 1) {
                redisTemplate.delete(key[0]);
            } else {
                redisTemplate.delete(Arrays.asList(key));
            }
        }
    }

    /**
     * 按表達(dá)式刪除緩存
     *
     * @param pattern 表達(dá)式
     */
    public void deleteAll(String pattern) {
        if (pattern == null || pattern.length() == 0) {
            throw new IllegalArgumentException("參數(shù)不正確");
        }
        redisTemplate.delete(scan(pattern));
    }

    /**
     * 查詢所有滿足表達(dá)式的key
     * (命令KEYS试溯,實(shí)際是利用SCAN命令實(shí)現(xiàn)蔑滓,不會(huì)產(chǎn)生阻塞)
     *
     * @param pattern 表達(dá)式
     * @return key
     */
    @SuppressWarnings("all")
    @NonNull
    public Set<String> scan(String pattern) {
        if (pattern == null || pattern.length() == 0) {
            throw new IllegalArgumentException("參數(shù)不正確");
        }
        return redisTemplate.execute((RedisCallback<Set<String>>) connection -> {
                    Set<String> keys = new HashSet<>();
                    Cursor<byte[]> cursor = connection.scan(ScanOptions.scanOptions().match(pattern).count(1000).build());
                    while (cursor.hasNext()) {
                        keys.add(new String(cursor.next()));
                    }
                    return keys;
                }
        );
    }


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

    /**
     * 放入緩存
     *
     * @param key   鍵
     * @param value 值
     */
    public void set(String key, Object value, long timeout, TimeUnit timeUnit) {
        redisTemplate.opsForValue().set(key, value, timeout, timeUnit);
    }

    /**
     * 放入緩存
     *
     * @param key   鍵
     * @param value 值
     */
    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    /**
     * 獲取緩存(string數(shù)據(jù)結(jié)構(gòu))
     * <p>
     * 通過(guò)泛型T指定緩存數(shù)據(jù)類(lèi)型
     *
     * @param key 鍵
     * @return 值
     * @see #getMap
     */
    @SuppressWarnings("unchecked")
    public <T> T get(String key) {
        return key == null ? null : (T) redisTemplate.opsForValue().get(key);
    }


    /**
     * 緩存普通鍵值對(duì),并設(shè)置失效時(shí)間
     *
     * @param key     鍵
     * @param value   值
     * @param seconds 時(shí)間(秒)遇绞,如果 time <= 0 則不設(shè)置失效時(shí)間
     */
    public void set(String key, Object value, int seconds) {
        if (seconds > 0) {
            redisTemplate.opsForValue().set(key, value, seconds, TimeUnit.SECONDS);
        } else {
            redisTemplate.opsForValue().set(key, seconds);
        }
    }

    /**
     * 當(dāng)key不存在時(shí)放入鍵值對(duì)
     * 如果已經(jīng)存在key返回false
     *
     * @param key   鍵
     * @param value 值
     * @return true/false
     */
    public Boolean setNx(String key, Object value) {
        return redisTemplate.opsForValue().setIfAbsent(key, value);
    }

    /**
     * 當(dāng)key不存在時(shí)放入鍵值對(duì)键袱,并在指定時(shí)間后自動(dòng)刪除
     * 如果已經(jīng)存在key返回false
     *
     * @param key   鍵
     * @param value 值
     * @return true/false
     */
    public Boolean setNx(String key, Object value, int time) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, time, TimeUnit.SECONDS);
    }

    /**
     * 遞增
     * 如果不存在key將自動(dòng)創(chuàng)建
     * (命令I(lǐng)NCR)
     *
     * @param key       鍵
     * @param increment 遞增大小
     * @return 遞增后的值
     */
    @SuppressWarnings("unchecked")
    public Long increment(String key, Long increment) {
        return redisTemplate.opsForValue().increment(key, increment);
    }

    /**
     * 遞增1
     * 如果不存在key將自動(dòng)創(chuàng)建
     * (命令I(lǐng)NCR)
     *
     * @param key 鍵
     * @return 遞增后的值
     */
    public Long increment(String key) {
        return redisTemplate.opsForValue().increment(key, 1);
    }

    /**
     * 遞減
     * 如果不存在key將自動(dòng)創(chuàng)建
     * (命令DECR)
     *
     * @param key       鍵
     * @param decrement 遞減大小
     * @return 遞減后的值
     */
    @SuppressWarnings("unchecked")
    public <T extends Number> T decrement(String key, T decrement) {
        return (T) redisTemplate.opsForValue().increment(key, -decrement.doubleValue());
    }

    /**
     * 遞減1
     * 如果不存在key將自動(dòng)創(chuàng)建
     * (命令DECR)
     *
     * @param key 鍵
     * @return 遞減后的值
     */
    public long decrement(String key) {
        return decrement(key, 1L);
    }

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

    /**
     * 往指定HashMap中添加一對(duì)鍵值對(duì),key不存在將自動(dòng)創(chuàng)建
     * (命令HMSET)
     *
     * @param name  HashMap的名字
     * @param key   添加的鍵
     * @param value 添加的值
     */
    public void putMap(String name, String key, Object value) {
        redisTemplate.opsForHash().put(name, key, value);
    }

    /**
     * 往指定HashMap中添加一對(duì)鍵值對(duì)摹闽,key不存在將自動(dòng)創(chuàng)建,key存在不進(jìn)行任何操作
     * (命令HSETNX)
     *
     * @param name  HashMap的名字
     * @param key   添加的鍵
     * @param value 添加的值
     */
    public Boolean putMapIfAbsent(String name, String key, Object value) {
        return redisTemplate.opsForHash().putIfAbsent(name, key, value);
    }

    /**
     * 往指定HashMap中添加另一個(gè)map
     * (命令HMSET)
     *
     * @param name HashMap的名字
     * @param map  值
     */
    public void putMap(String name, Map<String, ?> map) {
        redisTemplate.opsForHash().putAll(name, map);
    }

    /**
     * 從指定HashMap中獲取指定key的值
     * (命令HGET)
     *
     * @param mapName HashMap的名字(no null)
     * @param key     HashMap中的鍵(no null)
     * @param <T>     根據(jù)實(shí)際類(lèi)型自定義
     * @return 值
     */
    @SuppressWarnings("unchecked")
    public <T> T getMapItem(String mapName, String key) {
        return (T) redisTemplate.opsForHash().get(mapName, key);
    }

    /**
     * 從指定HashMap中獲取多個(gè)key的值
     * (命令HMGET)
     *
     * @param mapName HashMap的名字(no null)
     * @param key     HashMap中的鍵蹄咖,傳多個(gè)(no null)
     * @param <T>     根據(jù)實(shí)際類(lèi)型自定義
     * @return list
     */
    public <T> List<T> getMapItems(String mapName, String... key) {
        return redisTemplate.<String, T>opsForHash().multiGet(mapName, Arrays.asList(key));
    }

    /**
     * 獲取指定的HashMap
     * (命令HGETALL)
     *
     * @param mapName HashMap的名字(no null)
     * @return HashMap
     */
    public <T> Map<String, T> getMap(String mapName) {
        return redisTemplate.<String, T>opsForHash().entries(mapName);
    }


    /**
     * 刪除 HashMap 中的值
     * (命令HDEL)
     *
     * @param mapName HashMap的名字
     * @param keys    HashMap中的key(可以多個(gè),no null)
     */
    public void deleteHash(String mapName, Object... keys) {
        redisTemplate.opsForHash().delete(mapName, keys);
    }

    /**
     * 判斷指定 HashMap 中是否含有指定key
     *
     * @param hashMapName HashMap的名字(no null)
     * @param key         HashMap中的key(no null)
     * @return true / false
     */
    public Boolean exists(String hashMapName, String key) {
        return redisTemplate.opsForHash().hasKey(hashMapName, key);
    }

    /**
     * map 遞增指定值
     *
     * @param hashMapName HashMap的名字(no null)
     * @param key         HashMap中的key(no null)
     * @return true / false
     */
    public Long incrementMap(String hashMapName, String key, long delta) {
        return redisTemplate.opsForHash().increment(hashMapName, key, delta);
    }

    /**
     * 是否存在hashkey
     *
     * @param hashMapName HashMap的名字(no null)
     * @param hashKey     HashMap中的key(no null)
     * @return true / false
     */
    public Boolean hasMapKey(String hashMapName, Object hashKey) {
        return redisTemplate.opsForHash().hasKey(hashMapName, hashKey);
    }


    //    ============================== zset ==============================

    /**
     * 添加zset元素 有則覆蓋
     *
     * @param key
     * @param v
     * @param score
     * @return
     */
    public <T> Boolean zAdd(String key, T v, long score) {
        return redisTemplate.opsForZSet().add(key, v, score);
    }

    /**
     * 添加zset元素 有則覆蓋
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public Long zRemove(String key, long start, long end) {
        return redisTemplate.opsForZSet().removeRange(key, start, end);
    }

    /**
     * zset 元素個(gè)數(shù)
     *
     * @param key
     * @return
     */
    public Long zSize(String key) {
        return redisTemplate.opsForZSet().size(key);
    }

    /**
     * 自增zset score
     *
     * @param key
     * @param v
     * @param delta
     * @return
     */
    public <T> Double zIncrementScore(String key, T v, Double delta) {
        return redisTemplate.opsForZSet().incrementScore(key, v, delta);
    }

    /**
     * 獲取指定key的所有元素
     *
     * @param key
     * @param <T>
     * @return
     */
    public <T> Set<T> zAll(String key) {
        return (Set<T>) redisTemplate.opsForZSet().range(key, 0, -1);
    }

    /**
     * 獲取指定位置元素
     *
     * @param key
     * @param index
     * @param <T>
     * @return
     */
    public <T> T zGet(String key, int index) {
        Set<Object> objects = redisTemplate.opsForZSet().range(key, index, index + 1L);
        if (CollectionUtils.isEmpty(objects)) return null;
        return (T) objects.iterator().next();
    }

    /**
     * zset刪除指定元素
     *
     * @param key
     * @param v
     * @return
     */
    public Long zDel(String key, Object v) {
        return redisTemplate.opsForZSet().remove(key, v);
    }

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

    public Boolean sIsMember(String key, Object obj) {
        return redisTemplate.opsForSet().isMember(key, obj);
    }

    /**
     * 新增set 元素
     *
     * @param key
     * @param v
     * @return
     */
    public <T> Long sAdd(String key, T v) {
        return redisTemplate.opsForSet().add(key, v);
    }

    /**
     * 獲取set元素
     *
     * @param key
     * @return
     */
    public <T> Set<T> sGet(String key) {
        return (Set<T>) redisTemplate.opsForSet().members(key);
    }

    /**
     * 新增set元素
     *
     * @param key
     * @param values
     */
    public <T> void sAdd(String key, T... values) {
        redisTemplate.opsForSet().add(key, values);
    }

    /**
     * 移除set元素
     *
     * @param key
     * @param v
     */
    public <T> void srm(String key, T v) {
        redisTemplate.opsForSet().remove(key, v);
    }

    //    ============================== list ==============================

    /**
     * 從左邊塞入元素
     *
     * @param key
     * @param v
     */
    public <T> Long lPush(String key, T v) {
        return redisTemplate.opsForList().leftPush(key, v);
    }

    /**
     * 從右邊批量塞入元素
     *
     * @param key
     * @param vs
     */
    public <T> Long rPushAll(String key, T... vs) {
        return redisTemplate.opsForList().rightPushAll(key, vs);
    }

    /**
     * 刪除右邊的元素
     *
     * @param key
     * @param removeCount
     */
    public void rightRemove(String key, Long removeCount) {
        redisTemplate.opsForList().trim(key, 0, listSize(key) - removeCount);
    }

    /**
     * list集合中元素個(gè)數(shù)
     *
     * @param key
     */
    public Long listSize(String key) {
        return redisTemplate.opsForList().size(key);
    }

    /**
     * 批量查詢
     *
     * @param key
     */
    public <T> List<T> list(String key) {
        return (List<T>) redisTemplate.opsForList().range(key, 0, -1);
    }

    /**
     * 獲取redisTemplate钩骇,可實(shí)現(xiàn)更多操作
     *
     * @return RedisTemplate
     */
    public RedisTemplate<String, Object> getRedisTemplate() {
        return redisTemplate;
    }

    public <T> T execute(RedisScript<T> script, List<String> keys, Object... args) {
        return redisTemplate.execute(script, keys, args);
    }

    /**
     * 管道
     *
     * @param consumer
     * @param <T>
     * @return
     */
    @SuppressWarnings("unchecked")
    public <T> List<T> piPipelined(Consumer<RedisOperations<Object, Object>> consumer) {
        return (List<T>) redisTemplate.executePipelined(new SessionCallback<Object>() {
            @Override
            public Object execute(RedisOperations operations) throws DataAccessException {
                consumer.accept(operations);
                return null;
            }
        });
    }


    //    ============================== bitmap ==============================

    /**
     * bitmap設(shè)置指定位置 為 1或0
     * (命令 SETBIT)
     *
     * @param key    key
     * @param offset 偏移量
     * @param value  true / false
     */
    public boolean setBit(String key, long offset, boolean value) {
        return redisTemplate.opsForValue().setBit(key, offset, value);
    }

    /**
     * bitmap獲取指定位置結(jié)果
     * (命令 GETBIT)
     *
     * @param key    key
     * @param offset 偏移量
     * @return true / false
     */
    public boolean getBit(String key, long offset) {
        return redisTemplate.opsForValue().getBit(key, offset);
    }

    /**
     * bitmap 統(tǒng)計(jì)和比藻,所有位置為1的總數(shù)量
     * (命令 BITCOUNT)
     *
     * @param key key
     */
    public long bitCount(String key) {
        return redisTemplate.execute((RedisCallback<Long>) con -> con.bitCount(key.getBytes()));
    }

    public long bitPos(String key, boolean bit, Range<Long> range) {
        return redisTemplate.execute((RedisCallback<Long>) con -> con.bitPos(key.getBytes(), bit, range));
    }

    /**
     * bitField
     * (命令 BITFIELD)
     *
     * @param key                 key
     * @param bitFieldSubCommands demo: BitFieldSubCommands bitFieldSubCommands =BitFieldSubCommands.create().get(BitFieldSubCommands.BitFieldType.UINT_16).valueAt(0);
     */
    public List<Long> bitField(String key, BitFieldSubCommands bitFieldSubCommands) {
        return redisTemplate.opsForValue().bitField(key, bitFieldSubCommands);
    }

    @Nullable
    public RedisConnection getConnection() {
        final RedisConnectionFactory connectionFactory = redisTemplate.getConnectionFactory();
        if (connectionFactory == null) return null;
        return connectionFactory.getConnection();
    }
}

3.3 自定義緩存值序列化器

/**
 * spring cache redis 序列化器
 * 用redis緩存spring cache時(shí)使用铝量,解決了LocalDateTime序列化和反序列化問(wèn)題
 *
 * @author co
 * @since 2023-12-05 16:37:00
 */
public class RedisCacheValueSerializer extends GenericJackson2JsonRedisSerializer {
    public RedisCacheValueSerializer() {
        super();
    }

    @Override
    public Object deserialize(byte[] source) throws SerializationException {
        Object value = super.deserialize(source);
        if (value instanceof String) {
            String str = value == null ? "" : (String) value;
            // 以下為L(zhǎng)ocalDate和LocalDateTime的判斷和轉(zhuǎn)換倘屹,可以替換為其他更具效率的方式
            try {
                if (str.length() == 10) {
                    return LocalDate.parse(str, DATE_FORMATTER);
                }
                if (str.length() == 19) {
                    return LocalDateTime.parse(str, DATE_TIME_FORMATTER);
                }
            } catch (DateTimeParseException ex) {

            }
        }
        return value;
    }

    @Override
    public byte[] serialize(Object source) throws SerializationException {
        if (source instanceof LocalDateTime) {
            return super.serialize(((LocalDateTime) source).format(DATE_TIME_FORMATTER));
        }

        if (source instanceof LocalDate) {
            return super.serialize(((LocalDate) source).format(DATE_FORMATTER));
        }
        return super.serialize(source);
    }
}
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市慢叨,隨后出現(xiàn)的幾起案子纽匙,更是在濱河造成了極大的恐慌,老刑警劉巖拍谐,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烛缔,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡轩拨,警方通過(guò)查閱死者的電腦和手機(jī)践瓷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)亡蓉,“玉大人晕翠,你說(shuō)我怎么就攤上這事。” “怎么了淋肾?”我有些...
    開(kāi)封第一講書(shū)人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵硫麻,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我樊卓,道長(zhǎng)拿愧,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任碌尔,我火速辦了婚禮浇辜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘唾戚。我一直安慰自己奢赂,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布颈走。 她就那樣靜靜地躺著膳灶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪立由。 梳的紋絲不亂的頭發(fā)上轧钓,一...
    開(kāi)封第一講書(shū)人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音锐膜,去河邊找鬼毕箍。 笑死,一個(gè)胖子當(dāng)著我的面吹牛道盏,可吹牛的內(nèi)容都是我干的而柑。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼荷逞,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼媒咳!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起种远,我...
    開(kāi)封第一講書(shū)人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涩澡,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后坠敷,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體妙同,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年膝迎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了粥帚。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡限次,死狀恐怖芒涡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤拖陆,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布弛槐,位于F島的核電站,受9級(jí)特大地震影響依啰,放射性物質(zhì)發(fā)生泄漏乎串。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一速警、第九天 我趴在偏房一處隱蔽的房頂上張望叹誉。 院中可真熱鬧,春花似錦闷旧、人聲如沸长豁。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)匠襟。三九已至,卻和暖如春该园,著一層夾襖步出監(jiān)牢的瞬間酸舍,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來(lái)泰國(guó)打工里初, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留啃勉,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓双妨,卻偏偏與公主長(zhǎng)得像淮阐,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刁品,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容