spring boot 集成redis版本說明

spring boot 集成redis版本說明

官網文檔:https://docs.spring.io/spring-boot/docs/2.0.2.RELEASE/reference/htmlsingle/

當前版本是2.0.3(目前官網的當前版本也是2.0.3 {2018-06-25})

1.5.X版本redis依賴如下:

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

2.0.X版本redis依賴如下:

引用方式一:
<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>jcl-over-slf4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

說明:直接使用spring-data-redis ,引用jedis客戶端

引用方式二:
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
    <exclusions>
        <exclusion>
            <groupId>io.lettuce</groupId>
            <artifactId>lettuce-core</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
</dependency>

說明:默認情況下,Spring Boot starter(spring-boot-starter-data-redis)使用 Lettuce。您需要排除該依賴關係,幷包含Jedis。Spring Boot管理這些依賴關係,以便儘可能簡化此過程。

spring boot caching與redis集成方案

配置RedisConfig.java

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        return new JedisConnectionFactory();
    }
    
    /**
     * spring boot 2.0.x以上版本的使用方式
     * @param redisConnectionFactory
     * @return
     */
    @SuppressWarnings("rawtypes")
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        RedisCacheManager rcm = RedisCacheManager.builder(redisConnectionFactory).build();
        return rcm;
    }
    
   /**
     * 自定義生成redis-key
     *  lambda表達式
     * @return
     */
    @Override
    public KeyGenerator keyGenerator() {
        return (o, method, objects)->{
            StringBuilder sb = new StringBuilder();
            sb.append(o.getClass().getName()).append(".");
            sb.append(method.getName()).append(".");
            for (Object obj : objects) {
                sb.append(obj.toString());
            }
            System.out.println("keyGenerator=" + sb.toString());
            return sb.toString();
        };

    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);//注入redis數據源
        template.setKeySerializer(new StringRedisSerializer());//設置key的序列化方式
        template.setValueSerializer(new RedisObjectSerializer());//設置value的序列化方式
        return template;
    }
}

@EnableCaching 開啓caching功能 引入CachingConfigurationSelector自動配置,導入Caching相關注解攔截功能

CacheManager 由RedisCacheManager實現:RedisCacheManager.builder(redisConnectionFactory).build();(2.0.x版本)

配置RedisObjectSerializer序列化方式

/**
 * redis 序列化方式
 *
 *
 */
public class RedisObjectSerializer implements RedisSerializer<Object> {

  private Converter<Object, byte[]> serializer = new SerializingConverter();
  private Converter<byte[], Object> deserializer = new DeserializingConverter();

  static final byte[] EMPTY_ARRAY = new byte[0];

  public Object deserialize(byte[] bytes) {
    if (isEmpty(bytes)) {
      return null;
    }

    try {
      return deserializer.convert(bytes);
    } catch (Exception ex) {
      throw new SerializationException("Cannot deserialize", ex);
    }
  }

  public byte[] serialize(Object object) {
    if (object == null) {
      return EMPTY_ARRAY;
    }

    try {
      return serializer.convert(object);
    } catch (Exception ex) {
      return EMPTY_ARRAY;
    }
  }

  private boolean isEmpty(byte[] data) {
    return (data == null || data.length == 0);
  }
}

對key/value的存儲進行自定義的序列化

Spring cache的註解如何使用

在spring cache與redis集成之後,我們就可以使用spring cache自帶的註解功能

緩存的主要使用方式包括以下兩方面

  1. 緩存的聲明,需要根據項目需求來妥善的應用緩存 
  2. 緩存的配置方式,選擇需要的緩存支持,例如Ecache、redis、memercache等

@CacheConfig:該註解是可以將緩存分類,它是類級別的註解方式。 @CacheConfig(cacheNames = "xxx")統一聲明 @Cacheable(value="xxx")的屬性,簡單明瞭. @CacheConfig is a class-level annotation that allows to share the cache names,如果你在你的方法寫別的名字,那麼依然以方法的名字爲準。

eg: 
@CacheConfig(cacheNames = "user")
@Service
public class UserCacheRedisService {}

說明:
@CacheConfig(cacheNames = "user"), 使UserCacheRedisService的所有緩存註解, value值就都爲user。

@Cacheable:主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存 1.如果key不存在,執行方法體,並將結果更新到緩存中。 2.如果key存在,直接查詢緩存中的數據。

@Cacheable 作用和配置方法

@Cacheable
public List<User> selectAllUser(){
    log.info("selectAllUser execute");
    return data;
}

@Cacheable(value = "getUser", key = "#id")
public User getUser(int id){
    User user = new User();
    user.setId(id);
    user.setAge(20);
    user.setUsername("瑪雅文明");
    log.info("getUser execute");
    return user;
}

@CachePut:主要針對方法配置,能夠根據方法的請求參數對其結果進行緩存,和 @Cacheable 不同的是,它每次都會執行方法體

  1. 如果key存在,更新內容 
  2. 如果key不存在,插入內容。 @CachePut 作用和配置方法

@CachePut(value="saveOfUpdate", key = "\"user_\" + #user.id")
public User saveOfUpdate(User user){
    log.info("saveOfUpdate execute");
    return user;
}

@CachEvict: 主要針對方法配置,能夠根據一定的條件對緩存進行清空,執行方法體

@CacheEvict 作用和配置方法

/**
 * 清空指定key緩存
 * @param user
 */
@CacheEvict(value="saveOfUpdate", key="\"user_\" + #user.getId()")
public void clearUser(User user) {
    log.info("clearUser execute");
}

/**
 * allEntries:是否清空所有緩存內容,缺省爲 false,如果指定爲 true,則方法調用後將立即清空所有緩存
 */
@CacheEvict(value="saveOfUpdate", allEntries=true)
public void flushCacle() {
    log.info("flushCacle execute");
}

條件緩存 下面提供一些常用的條件緩存

//@Cacheable將在執行方法之前( #result還拿不到返回值)判斷condition,如果返回true,則查緩存; 
@Cacheable(value = "user", key = "#id", condition = "#id lt 10")
public User conditionFindById(final Long id)  

//@CachePut將在執行完方法後(#result就能拿到返回值了)判斷condition,如果返回true,則放入緩存; 
@CachePut(value = "user", key = "#id", condition = "#result.username ne 'zhang'")  
public User conditionSave(final User user)   

//@CachePut將在執行完方法後(#result就能拿到返回值了)判斷unless,如果返回false,則放入緩存;(即跟condition相反)
@CachePut(value = "user", key = "#user.id", unless = "#result.username eq 'zhang'")
public User conditionSave2(final User user)   

//@CacheEvict, beforeInvocation=false表示在方法執行之後調用(#result能拿到返回值了);且判斷condition,如果返回true,則移除緩存;
@CacheEvict(value = "user", key = "#user.id", beforeInvocation = false, condition = "#result.username ne 'zhang'")  
public User conditionDelete(final User user)   

@Caching

有時候我們可能組合多個Cache註解使用;比如用戶新增成功後,我們要添加id–>user;username—>user;email—>user的緩存;此時就需要@Caching組合多個註解標籤了。

@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
public User save(User user) {

自定義緩存註解

@Caching組合,會讓方法顯的較臃腫,可以通過自定義註解把這些註解組合到一個註解中,如:

@Caching(put = {
@CachePut(value = "user", key = "#user.id"),
@CachePut(value = "user", key = "#user.username"),
@CachePut(value = "user", key = "#user.email")
})
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface UserSaveCache {
}

使用:
@UserSaveCache
public User save(User user){
 //do something
}

擴展

比如findByUsername時,不應該只放username–>user,應該連同id—>user和email—>user一起放入;這樣下次如果按照id查找直接從緩存中就命中了

@Caching(
    cacheable = {
       @Cacheable(value = "user", key = "#username")
    },
    put = {
       @CachePut(value = "user", key = "#result.id", condition = "#result != null"),
       @CachePut(value = "user", key = "#result.email", condition = "#result != null")
    }
)
public User findByUsername(final String username) {
    System.out.println("cache miss, invoke find by username, username:" + username);
    for (User user : users) {
        if (user.getUsername().equals(username)) {
            return user;
        }
    }
    return null;
}

其實對於:id—>user;username—->user;email—>user;更好的方式可能是:id—>user;username—>id;email—>id;保證user只存一份;如:

@CachePut(value="cacheName", key="#user.username", cacheValue="#user.username")  
public void save(User user)   


@Cacheable(value="cacheName", key="#user.username", cacheValue="#caches[0].get(#caches[0].get(#username).get())")  
public User findByUsername(String username)  

Redis 序列化方式以及相互之間的比較

當我們的數據存儲到Redis的時候,我們的鍵(key)和值(value)都是通過Spring提供的Serializer序列化到數據庫的。

RedisTemplate默認使用的是JdkSerializationRedisSerializer
StringRedisTemplate默認使用的是StringRedisSerializer

Spring Data Redis(1.4.7版本)爲我們提供了下面的Serializer:

GenericToStringSerializer、GenericJackson2JsonRedisSerializer、Jackson2JsonRedisSerializer、JacksonJsonRedisSerializer、JdkSerializationRedisSerializer、StringRedisSerializer。

序列化方式對比:

* GenericToStringSerializer:可以將任何對象泛化爲字符串並序列化
* GenericJackson2JsonRedisSerializer:使用Jackson庫將對象序列化爲JSON字符串。
    優點:是速度快,序列化後的字符串短小精悍,不需要實現Serializable接口,會在json中加入@class屬性,類的全路徑包名,方便反序列化
    缺點:也非常致命,那就是此類的構造函數中有一個類型參數,必須提供要序列化對象的類型信息(.class對象)。 通過查看源代碼,發現其只在反序列化過程中用到了類型信息,時間消耗比JDK長。
* JdkSerializationRedisSerializer: 使用JDK提供的序列化功能。 
    優點:是反序列化時不需要提供類型信息(class),最高效的
    缺點:是需要實現Serializable接口,還有序列化後的結果非常龐大,是JSON格式的5倍左右,這樣就會消耗redis服務器的大量內存。
* Jackson2JsonRedisSerializer: 使用Jackson庫將對象序列化爲JSON字符串。
    優點:是速度快,序列化後的字符串短小精悍,不需要實現Serializable接口。
    缺點:也非常致命,那就是此類的構造函數中有一個類型參數,必須提供要序列化對象的類型信息(.class對象)。 通過查看源代碼,發現其只在反序列化過程中用到了類型信息,時間消耗比JDK長。
         無法指定List容器裏面元素的類,所以反序列化時直接將元素反序列化成了LinkedHashMap導致返回結果的時候強制類型轉化報錯。
* StringRedisSerializer:簡單的字符串序列化,一般如果key-value都是string的話,使用StringRedisSerializer就可以了

JedisConnectionFactory配置介紹

一.簡單配置

application.properties

# REDIS (RedisProperties)
# Redis數據庫索引(默認爲0)
spring.redis.database=0  
# Redis服務器地址
spring.redis.host=192.168.127.131  
# Redis服務器連接端口
spring.redis.port=6379  
# Redis服務器連接密碼(默認爲空)
spring.redis.password=q1w2e3r4  
# 連接池最大連接數(使用負值表示沒有限制)
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

RedisConfig配置

@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {
    
    /**
     * 默認,只能連接本地,連接其他網絡添加
     * jedisConnectionFactory.setHostName(host);
     * jedisConnectionFactory.setPort(port);
     * 
     */
    @Bean
    JedisConnectionFactory jedisConnectionFactory() {
        return new JedisConnectionFactory(); 
    }

    
    @SuppressWarnings("rawtypes")
    @Bean
    @Bean
        public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
            RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
            redisCacheManager.setDefaultExpiration(300);
            return redisCacheManager;
        }

    /**
     * 自定義生成redis-key
     *  lambda表達式
     * @return
     */
    @Override
    public KeyGenerator keyGenerator() {
        return (o, method, objects)->{
            StringBuilder sb = new StringBuilder();
            sb.append(o.getClass().getName()).append(".");
            sb.append(method.getName()).append(".");
            for (Object obj : objects) {
                sb.append(obj.toString());
            }
            System.out.println("keyGenerator=" + sb.toString());
            return sb.toString();
        };

    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);//注入redis數據源
        template.setKeySerializer(new StringRedisSerializer());//設置key的序列化方式
        //template.setValueSerializer(new RedisObjectSerializer());//設置value的序列化方式一
        template.setValueSerializer(jackRedisSerializer());//設置value的序列化方式二 推薦這種方式,這種方式不會亂碼
        return template;
    }

    public Jackson2JsonRedisSerializer jackRedisSerializer(){
        // 使用Jackson2JsonRedisSerialize 替換默認序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        return jackson2JsonRedisSerializer;
    }
    
}

二.帶jedispool的配置


@Configuration
@EnableCaching
public class RedisConfig extends CachingConfigurerSupport {

    @Value("${spring.redis.host}")
    private String host;

    @Value("${spring.redis.password}")
    private String password;

    @Value("${spring.redis.port}")
    private int port;

    @Value("${spring.redis.timeout}")
    private int timeout;

    @Value("${spring.redis.database}")
    private int defaultDatabaseIndex;

    @Value("${spring.redis.pool.max-idle}")
    private int maxIdle;

    @Value("${spring.redis.pool.min-idle}")
    private int minIdle;

    @Value("${spring.redis.pool.max-active}")
    private int maxActive;

    @Value("${spring.redis.pool.max-wait}")
    private long maxWaitMillis;


    @Bean
    public RedisConnectionFactory connectionFactory() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();
        jedisConnectionFactory.setHostName(host);
        jedisConnectionFactory.setPort(port);
        jedisConnectionFactory.setTimeout(timeout);
        if (!StringUtils.isEmpty(password)) {
            jedisConnectionFactory.setPassword(password);
        }
        if (defaultDatabaseIndex != 0) {
            jedisConnectionFactory.setDatabase(defaultDatabaseIndex);
        }
        jedisConnectionFactory.setPoolConfig(poolConfig());
        // 初始化連接pool
        jedisConnectionFactory.afterPropertiesSet();
        return jedisConnectionFactory;
    }

    public JedisPoolConfig poolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxTotal(maxActive);
        jedisPoolConfig.setMinIdle(minIdle);
        jedisPoolConfig.setMaxIdle(maxIdle);
        jedisPoolConfig.setMaxWaitMillis(maxWaitMillis);
        return jedisPoolConfig;
    }

    /**
     * spring boot 1.3.x~1.5.x版本
     * @param redisTemplate
     * @return
     */
    // 定製緩存管理器的屬性,默認提供的CacheManager對象可能不能滿足需要
    // 因此建議依賴業務和技術上的需求,自行做一些擴展和定製
    // 這樣就可以在使用Spring4中的@Cacheable、@CachePut、@CacheEvict 註解了
    // 使用cache註解管理redis緩存
    @Bean
    public CacheManager cacheManager(@SuppressWarnings("rawtypes") RedisTemplate redisTemplate) {
        RedisCacheManager redisCacheManager = new RedisCacheManager(redisTemplate);
        redisCacheManager.setDefaultExpiration(300);
        return redisCacheManager;
    }

    /**
     * 自定義生成redis-key
     *  lambda表達式
     * @return
     */
    @Override
    public KeyGenerator keyGenerator() {
        return (o, method, objects)->{
            StringBuilder sb = new StringBuilder();
            sb.append(o.getClass().getName()).append(".");
            sb.append(method.getName()).append(".");
            for (Object obj : objects) {
                sb.append(obj.toString());
            }
            System.out.println("keyGenerator=" + sb.toString());
            return sb.toString();
        };

    }

    @Bean
    public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate template = new RedisTemplate();
        template.setConnectionFactory(redisConnectionFactory);//注入redis數據源
        template.setKeySerializer(new StringRedisSerializer());//設置key的序列化方式
        //template.setValueSerializer(new RedisObjectSerializer());//設置value的序列化方式一
        template.setValueSerializer(jackRedisSerializer());//設置value的序列化方式二 推薦這種方式,這種方式不會亂碼
        return template;
    }

    public Jackson2JsonRedisSerializer jackRedisSerializer(){
        // 使用Jackson2JsonRedisSerialize 替換默認序列化
        Jackson2JsonRedisSerializer jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(Object.class);

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        objectMapper.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);

        jackson2JsonRedisSerializer.setObjectMapper(objectMapper);

        return jackson2JsonRedisSerializer;
    }
}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章