【SpringBoot實戰專題】「開發實戰系列」從零開始教你舒服的使用RedisTemplate操作Redis資料

2023-01-11 15:05:10

SpringBoot快速操作Redis資料

在SpringBoot框架中提供了spring-boot-starter-data-redis的依賴元件進行操作Redis服務,當引入了該元件之後,只需要設定Redis的設定即可進行連結Redis服務並且進行操作Redis服務資料。

針對於不同的版本有了不同的底層使用者端的支援的底層使用者端框架是不同的:目前常見的使用者端為Jedis和Lettuce。

低版本SpringBoot支援的Jedis

Jedis是很常用的Redis的Java 實現的使用者端。支援基本的資料型別如:String、Hash、List、Set、Sorted Set。

特點:使用阻塞的 I/O,方法呼叫同步,程式流需要等到 socket 處理完 I/O 才能執行,不支援非同步操作。Jedis 使用者端範例不是執行緒安全的,需要通過連線池來使用 Jedis。

高版本版本SpringBoot支援的Lettuce

Lettuce使用者端主要用於執行緒安全同步,非同步和響應使用,支援叢集,Sentinel,管道和編碼器。

基於 Netty 框架的事件驅動的通訊層,其方法呼叫是非同步的。Lettuce 的 API 是執行緒安全的,所以可以操作單個 Lettuce 連線來完成各種操作。

spring-data-redis針對jedis提供瞭如下功能:

Spring Boot 的 spring-boot-starter-data-redis 為 Redis 的相關操作提供了一個高度封裝的 RedisTemplate 類,而且對每種型別的資料結構都進行了歸類,實現連線池自動管理,提供了一個高度封裝的「RedisTemplate」類。 針對jedis/Lettuce使用者端中大量api進行了歸類封裝,將同一型別操作封裝為operation介面。

通用的介面型別工廠方法

提供了對key的「bound」(繫結)便捷化操作API,可以通過bound封裝指定的key,然後進行一系列的操作而無須「顯式」的再次指定Key,即BoundKeyOperations:

  • ValueOperations - BoundValueOperations:String型別的簡單K-V操作
  • SetOperations - BoundSetOperations:set型別資料操作
  • ZSetOperations - BoundListOperations:zset型別資料操作
  • HashOperations - BoundSetOperations:針對map型別的資料操作
  • ListOperations - BoundHashOperations:針對list型別的資料操作

序列化/反序列化的擴充套件機制

針對資料的「序列化/反序列化」,提供了多種可選擇策略(RedisSerializer)

JdkSerializationRedisSerializer

POJO物件的存取場景,使用JDK本身序列化機制,將pojo類通過ObjectInputStream/ObjectOutputStream進行序列化操作,最終redis-server中將儲存位元組序列。是目前最常用的序列化策略。

StringRedisSerializer

Key或者value為字串的場景,根據指定的charset對資料的位元組序列編碼成string,是「new String(bytes, charset)」和「string.getBytes(charset)」的直接封裝。是最輕量級和高效的策略。

JacksonJsonRedisSerializer

jackson-json工具提供了javabean與json之間的轉換能力,可以將pojo範例序列化成json格式儲存在redis中,也可以將json格式的資料轉換成pojo範例。因為jackson工具在序列化和反序列化時,需要明確指定Class型別,因此此策略封裝起來稍微複雜。【需要jackson-mapper-asl工具支援】

Jackson2JsonRedisSerializer

使用Jackson庫將物件序列化為JSON字串。優點是速度快,序列化後的字串短小精悍,不需要實現Serializable介面。但缺點也非常致命,那就是此類別建構函式中有一個型別引數,必須提供要序列化物件的型別資訊(.class物件)。 通過檢視原始碼,發現其只在反序列化過程中用到了型別資訊。

OxmSerializer

提供了將javabean與xml之間的轉換能力,目前可用的三方支援包括jaxb,apache-xmlbeans;redis儲存的資料將是xml工具。不過使用此策略,程式設計將會有些難度,而且效率最低;不建議使用。【需要spring-oxm模組的支援】

擴充套件第三方序列化工具

當然了除了以上這幾種基本的序列化器之外您還可以進行自定義一些更加優秀、速度更塊的序列化方式,例如:FastJsonRedisSerializer和KryoRedisSerializer、FSTRedisSerializer等。

RedisSerializer介面

RedisSerializer 基礎介面定義了將物件轉換為位元組陣列(二進位制資料)的序列化和反序列化方法。建議將實現設計為在序列化和反序列化端處理空物件/空位元組陣列。注意,Redis 不接受空鍵或空值,但可以返回 null(對於不存在的鍵)。

RedisSerializer 介面方法定義

序列化

序列化方法定義如下:

byte[] serialize(T t)

該方法將給定物件 t 序列化為二進位制資料,及位元組陣列。注意:物件 t 和返回值可以為 null。

反序列化

反序列化方法定義如下:

T deserialize(byte[] bytes)

該方法將從給定的二進位制資料(位元組陣列)反序列化為一個物件。注意:bytes 位元組陣列和返回值 T 均可以為 null。

注意:如果上面的 serialize() 和 deserialize() 方法在執行時報錯,將丟擲org.springframework.data.redis.serializer.SerializationException 異常。

引入spring-boot-starter-data-redis元件

springboot 與redis的整合,pom檔案,依賴如下:

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

設定對應的application.properties檔案

針對於設定我們按照jedis的設定為基礎案例,如下所示。

# 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.pool.max-active=8  
# 連線池最大阻塞等待時間(使用負值表示沒有限制)
spring.redis.pool.max-wait=-1  
# 連線池中的最大空閒連線
spring.redis.pool.max-idle=8  
# 連線池中的最小空閒連線
spring.redis.pool.min-idle=0  
# 連線超時時間(毫秒)
spring.redis.timeout=0 

對應的SpringBoot-Redis的核心設定類

此處需要定義RedisTemplate物件的設定類,其中需要設定對應的RedisConnectionFactory物件類以及對應型別的序列化和反序列化元件起。如下所示

定義對應的redisTemplate物件類

預設是JDK的序列化策略,這裡設定redisTemplate採用的是Jackson2JsonRedisSerializer的序列化策略,引數為redisConnectionFactory。

    @Bean
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory){
        //使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值(預設使用JDK的序列化方式)
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        // 指定要序列化的域,field,get和set,以及修飾符範圍,ANY是都有包括private和public
        om.setVisibility(PropertyAccessor.ALL,JsonAutoDetect.Visibility.ANY);
        // 指定序列化輸入的型別,類必須是非final修飾的,final修飾的類,比如String,Integer等會丟擲異常
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        // 設定連線工廠
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        //使用StringRedisSerializer來序列化和反序列化redis的key值
        //redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setKeySerializer(jackson2JsonRedisSerializer);
        // 值採用json序列化
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashKeySerializer(jackson2JsonRedisSerializer);
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

定義對應的StringRedisTemplate物件類

但是對於 string 型別的資料,Spring Boot 還專門提供了 StringRedisTemplate 類,而且官方也建議使用該類來操作 String 型別的資料。stringRedisTemplate預設採用的是String的序列化策略。

    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory redisConnectionFactory){
        StringRedisTemplate stringRedisTemplate = new StringRedisTemplate();
        stringRedisTemplate.setConnectionFactory(redisConnectionFactory);
        return stringRedisTemplate;
    }
StringRedisTemplate和 RedisTemplate 又有啥區別呢?
  • RedisTemplate 是一個泛型類,而 StringRedisTemplate 不是,後者只能對鍵和值都為 String 型別的資料進行操作,而前者則可以操作任何型別。

  • 兩者的資料是不共通的,StringRedisTemplate 只能管理 StringRedisTemplate 裡面的資料,RedisTemplate 只能管理 RedisTemplate 中 的資料。

定義組合序列化方式

key採用String序列化,value使用jackson序列化,如下程式碼所示。

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
        RedisTemplate<String, Object> template = new RedisTemplate<String, Object>();
        template.setConnectionFactory(factory);
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
        // key採用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也採用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式採用jackson
        template.setValueSerializer(jackson2JsonRedisSerializer);
        // hash的value序列化方式採用jackson
        template.setHashValueSerializer(jackson2JsonRedisSerializer);
        template.afterPropertiesSet();
        return template;
    }

定義RedisTemplate的腳手架

將形成一個快速運算元據的工具類,將各種型別的操作類進行封裝處理控制。

  • HashOperations:對hash型別的資料操作
  • ValueOperations:對redis字串型別資料操作
  • ListOperations:對連結串列型別的資料操作
  • SetOperations:對無序集合型別的資料操作
  • ZSetOperations:對有序集合型別的資料操作

將以上各種型別的類直接進行暴漏,減少呼叫鏈路的路徑長度。

  /**
     * 對hash型別的資料操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public HashOperations<String, String, Object> hashOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForHash();
    }

    /**
     * 對redis字串型別資料操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ValueOperations<String, Object> valueOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForValue();
    }

    /**
     * 對連結串列型別的資料操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ListOperations<String, Object> listOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForList();
    }

    /**
     * 對無序集合型別的資料操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public SetOperations<String, Object> setOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForSet();
    }

    /**
     * 對有序集合型別的資料操作
     * @param redisTemplate
     * @return
     */
    @Bean
    public ZSetOperations<String, Object> zSetOperations(RedisTemplate<String, Object> redisTemplate) {
        return redisTemplate.opsForZSet();
    }

定義基礎層的操作處理定義RedisSupport

除了以上這幾種型別的操作之外,還有一些基礎相關的核心操作類,包含重新命名,轉移以及情況整個庫的操作、設定TTL生命週期等。

@Component
public class RedisSupport {

	@Autowired
    private RedisTemplate<String, String> redisTemplate;

    /**
     * 預設過期時長,單位:秒
     */
    public static final long DEFAULT_EXPIRE = 60 * 60 * 24;

    /**
     * 不設定過期時長
     */
    public static final long NOT_EXPIRE = -1;

    public boolean existsKey(String key) {
        return redisTemplate.hasKey(key);
    }

    /**
     * 重名名key,如果newKey已經存在,則newKey的原值被覆蓋
     *
     * @param oldKey
     * @param newKey
     */
    public void renameKey(String oldKey, String newKey) {
        redisTemplate.rename(oldKey, newKey);
    }

    /**
     * newKey不存在時才重新命名
     *
     * @param oldKey
     * @param newKey
     * @return 修改成功返回true
     */
    public boolean renameKeyNotExist(String oldKey, String newKey) {
        return redisTemplate.renameIfAbsent(oldKey, newKey);
    }

    /**
     * 刪除key
     *
     * @param key
     */
    public void deleteKey(String key) {
        redisTemplate.delete(key);
    }

    /**
     * 刪除多個key
     *
     * @param keys
     */
    public void deleteKey(String... keys) {
        Set<String> kSet = Stream.of(keys).map(k -> k).collect(Collectors.toSet());
        redisTemplate.delete(kSet);
    }

    /**
     * 刪除Key的集合
     *
     * @param keys
     */
    public void deleteKey(Collection<String> keys) {
        Set<String> kSet = keys.stream().map(k -> k).collect(Collectors.toSet());
        redisTemplate.delete(kSet);
    }

    /**
     * 設定key的生命週期
     *
     * @param key
     * @param time
     * @param timeUnit
     */
    public void expireKey(String key, long time, TimeUnit timeUnit) {
        redisTemplate.expire(key, time, timeUnit);
    }

    /**
     * 指定key在指定的日期過期
     *
     * @param key
     * @param date
     */
    public void expireKeyAt(String key, Date date) {
        redisTemplate.expireAt(key, date);
    }

    /**
     * 查詢key的生命週期
     *
     * @param key
     * @param timeUnit
     * @return
     */
    public long getKeyExpire(String key, TimeUnit timeUnit) {
        return redisTemplate.getExpire(key, timeUnit);
    }

    /**
     * 將key設定為永久有效
     *
     * @param key
     */
    public void persistKey(String key) {
        redisTemplate.persist(key);
    }
}

至此整體對應的RedisTemplate物件的封裝和擴充套件就到這裡,可以把程式碼介入到你的專案裡面,非常方便的進行操作Redis了,是不是很OK呢?