1. 程式人生 > >1101-springboot之使用redistemplate優雅地操作redis

1101-springboot之使用redistemplate優雅地操作redis

springboot之使用redistemplate優雅地操作redis

概述

本文內容主要

  • 關於spring-redis
  • 關於redis的key設計
  • redis的基本資料結構
  • 介紹redis與springboot的整合
  • sringboot中的redistemplate的使用

之前看了很多部落格,大都都只是粗略的介紹,這裡想要記錄的全面一些,也算是一個學習的過程
首發於我的個人部落格:janti的個人部落格

關於spring-redis

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

1. 連線池自動管理,提供了一個高度封裝的“RedisTemplate”類

2. 針對jedis客戶端中大量api進行了歸類封裝,將同一型別操作封裝為operation介面

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

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

BoundValueOperations
BoundSetOperations
BoundListOperations
BoundSetOperations
BoundHashOperations

4. 將事務操作封裝,有容器控制。

5. 針對資料的“序列化/反序列化”,提供了多種可選擇策略(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工具支援】

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

如果你的資料需要被第三方工具解析,那麼資料應該使用StringRedisSerializer而不是JdkSerializationRedisSerializer。

關於key的設計

key的存活時間:

無論什麼時候,只要有可能就利用key超時的優勢。一個很好的例子就是儲存一些諸如臨時認證key之類的東西。當你去查詢一個授權key時——以OAUTH為例——通常會得到一個超時時間。
這樣在設定key的時候,設成同樣的超時時間,Redis就會自動為你清除。

關係型資料庫的redis

1: 把表名轉換為key字首 如, tag:
2: 第2段放置用於區分割槽key的欄位--對應mysql中的主鍵的列名,如userid
3: 第3段放置主鍵值,如2,3,4...., a , b ,c
4: 第4段,寫要儲存的列名
例:user:userid:9:username

Redis的資料型別

String字串

  • string是redis最基本的型別,一個key對應一個value。
  • string型別是二進位制安全的。意思是redis的string可以包含任何資料。比如jpg圖片或者序列化的物件 。
  • string型別是Redis最基本的資料型別,一個鍵最大能儲存512MB。
  • String型別的操作參考

連結串列

  • redis列表是簡單的字串列表,排序為插入的順序。列表的最大長度為2^32-1。
  • redis的列表是使用連結串列實現的,這意味著,即使列表中有上百萬個元素,增加一個元素到列表的頭部或尾部的操作都是在常量的時間完成。
  • 可以用列表獲取最新的內容(像帖子,微博等),用ltrim很容易就會獲取最新的內容,並移除舊的內容。
  • 用列表可以實現生產者消費者模式,生產者呼叫lpush新增項到列表中,消費者呼叫rpop從列表中提取,如果沒有元素,則輪詢去獲取,或者使用brpop等待生產者新增項到列表中。

  • List型別的操作參考

集合

  • redis集合是無序的字串集合,集合中的值是唯一的,無序的。可以對集合執行很多操作,例如,測試元素是否存在,對多個集合執行交集、並集和差集等等。
  • 我們通常可以用集合儲存一些無關順序的,表達物件間關係的資料,例如使用者的角色,可以用sismember很容易就判斷使用者是否擁有某個角色。
  • 在一些用到隨機值的場合是非常適合的,可以用 srandmember/spop 獲取/彈出一個隨機元素。
    同時,使用@EnableCaching開啟宣告式快取支援,這樣就可以使用基於註解的快取技術。註解快取是一個對快取使用的抽象,通過在程式碼中新增下面的一些註解,達到快取的效果。
  • Set型別的操作參考

ZSet 有序集合

  • 有序集合由唯一的,不重複的字串元素組成。有序集合中的每個元素都關聯了一個浮點值,稱為分數。可以把有序看成hash和集合的混合體,分數即為hash的key。

  • 有序集合中的元素是按序儲存的,不是請求時才排序的。
  • ZSet型別的操作型別

Hash-雜湊

  • redis的雜湊值是字串欄位和字串之間的對映,是表示物件的完美資料型別。

  • 雜湊中的欄位數量沒有限制,所以可以在你的應用程式以不同的方式來使用雜湊。

  • Hash型別的操作參考

springboot 與redis的整合

pom檔案

依賴如下:

   <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.5.1.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencies>
        <!-- spring boot 配置 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

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

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

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


    </dependencies>

application.properties

# 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  

redisTemplate的配置

新建一個redisConfig類,進行相關bean的配置:

package com.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CachingConfigurerSupport;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.*;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * @author janti
 * reids 相關bean的配置
 */
@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    /**
     * 選擇redis作為預設快取工具
     * @param redisTemplate
     * @return
     */
    @Bean
    public CacheManager cacheManager(RedisTemplate redisTemplate) {
        RedisCacheManager rcm = new RedisCacheManager(redisTemplate);
        return rcm;
    }

    /**
     * retemplate相關配置
     * @param factory
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {

        RedisTemplate<String, Object> template = new RedisTemplate<>();
        // 配置連線工廠
        template.setConnectionFactory(factory);

        //使用Jackson2JsonRedisSerializer來序列化和反序列化redis的value值(預設使用JDK的序列化方式)
        Jackson2JsonRedisSerializer jacksonSeial = 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);
        jacksonSeial.setObjectMapper(om);

        // 值採用json序列化
        template.setValueSerializer(jacksonSeial);
        //使用StringRedisSerializer來序列化和反序列化redis的key值
        template.setKeySerializer(new StringRedisSerializer());

        // 設定hash key 和value序列化模式
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(jacksonSeial);
        template.afterPropertiesSet();

        return template;
    }

    /**
     * 對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();
    }
}

  • spring-redis中使用了RedisTemplate來進行redis的操作,通過泛型的K和V設定鍵值對的物件型別。這裡使用了string作為key的物件型別,值為Object。

  • 對於Object,spring-redis預設使用了jdk自帶的序列化,不推薦使用默認了。所以使用了json的序列化方式

  • 對spring-redis對redis的五種資料型別也有支援
  • HashOperations:對hash型別的資料操作
  • ValueOperations:對redis字串型別資料操作
  • ListOperations:對連結串列型別的資料操作
  • SetOperations:對無序集合型別的資料操作
  • ZSetOperations:對有序集合型別的資料操作

redis操作的工具類

package com.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Date;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Component
public class RedisService {
    @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);
    }


}

redis的key工具類

package com.util;

/**
 * redisKey設計
 */
public class RedisKeyUtil {

    /**
     * redis的key
     * 形式為:
     * 表名:主鍵名:主鍵值:列名
     *
     * @param tableName 表名
     * @param majorKey 主鍵名
     * @param majorKeyValue 主鍵值
     * @param column 列名
     * @return
     */
    public static String getKeyWithColumn(String tableName,String majorKey,String majorKeyValue,String column){
        StringBuffer buffer = new StringBuffer();
        buffer.append(tableName).append(":");
        buffer.append(majorKey).append(":");
        buffer.append(majorKeyValue).append(":");
        buffer.append(column);
        return buffer.toString();
    }
    /**
     * redis的key
     * 形式為:
     * 表名:主鍵名:主鍵值
     *
     * @param tableName 表名
     * @param majorKey 主鍵名
     * @param majorKeyValue 主鍵值
     * @return
     */
    public static String getKey(String tableName,String majorKey,String majorKeyValue){
        StringBuffer buffer = new StringBuffer();
        buffer.append(tableName).append(":");
        buffer.append(majorKey).append(":");
        buffer.append(majorKeyValue).append(":");
        return buffer.toString();
    }
}

如何使用?

測試程式碼

新建一個實體類:

package com.domain;

public class UserVo {

    public  static final String Table = "t_user";

    private String name;
    private String address;
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }


    @Override
    public String toString() {
        return "UserVo{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", age=" + age +
                '}';
    }
}

再新建一個測試類:

package com.config;

import com.domain.UserVo;
import com.service.RedisService;
import com.util.RedisKeyUtil;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.*;
import org.springframework.test.context.junit4.SpringRunner;

import javax.annotation.Resource;

import java.util.Set;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisConfigTest {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisTemplate redisTemplate;

    @Resource
    private ValueOperations<String,Object> valueOperations;

    @Autowired
    private HashOperations<String, String, Object> hashOperations;

    @Autowired
    private ListOperations<String, Object> listOperations;

    @Autowired
    private SetOperations<String, Object> setOperations;

    @Autowired
    private ZSetOperations<String, Object> zSetOperations;

    @Resource
    private RedisService redisService;

    @Test
    public void testObj() throws Exception{
        UserVo userVo = new UserVo();
        userVo.setAddress("上海");
        userVo.setName("測試dfas");
        userVo.setAge(123);
        ValueOperations<String,Object> operations = redisTemplate.opsForValue();
        redisService.expireKey("name",20, TimeUnit.SECONDS);
        String key = RedisKeyUtil.getKey(UserVo.Table,"name",userVo.getName());
        UserVo vo = (UserVo) operations.get(key);
        System.out.println(vo);
    }

    @Test
    public void testValueOption( )throws  Exception{
        UserVo userVo = new UserVo();
        userVo.setAddress("上海");
        userVo.setName("jantent");
        userVo.setAge(23);
        valueOperations.set("test",userVo);

        System.out.println(valueOperations.get("test"));
    }

    @Test
    public void testSetOperation() throws Exception{
        UserVo userVo = new UserVo();
        userVo.setAddress("北京");
        userVo.setName("jantent");
        userVo.setAge(23);
        UserVo auserVo = new UserVo();
        auserVo.setAddress("n櫃昂周");
        auserVo.setName("antent");
        auserVo.setAge(23);
        setOperations.add("user:test",userVo,auserVo);
        Set<Object> result = setOperations.members("user:test");
        System.out.println(result);
    }

    @Test
    public void HashOperations() throws Exception{
        UserVo userVo = new UserVo();
        userVo.setAddress("北京");
        userVo.setName("jantent");
        userVo.setAge(23);
        hashOperations.put("hash:user",userVo.hashCode()+"",userVo);
        System.out.println(hashOperations.get("hash:user",userVo.hashCode()+""));
    }

    @Test
    public void  ListOperations() throws Exception{
        UserVo userVo = new UserVo();
        userVo.setAddress("北京");
        userVo.setName("jantent");
        userVo.setAge(23);
//        listOperations.leftPush("list:user",userVo);
//        System.out.println(listOperations.leftPop("list:user"));
        // pop之後 值會消失
        System.out.println(listOperations.leftPop("list:user"));
    }
}

註解快取的使用

  • @Cacheable:在方法執行前Spring先檢視快取中是否有資料,如果有資料,則直接返回快取資料;沒有則呼叫方法並將方法返回值放進快取。

  • @CachePut:將方法的返回值放到快取中。

  • @CacheEvict:刪除快取中的資料。

最後所有的程式碼都被上傳到我的github喜歡的話,給個start

參考:
Redis 學習(二) —— 資料型別及操作
安裝參考