Skip to content

Spring-Data-Redis+SpringCache

Spring-Data-Redis

Spring-data-redis是spring大家族的一部分,提供了在srping应用中通过简单的配置访问redis服务,对reids底层开发包(Jedis, JRedis, and RJC)进行了高度封装,RedisTemplate提供了redis各种操作、异常处理及序列化,支持发布订阅,并对spring 3.1 cache进行了实现。

基础配置

依赖

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>fastjson</artifactId>
      <version>1.2.61</version>
    </dependency>

application.yml

spring:
  redis:
    host: 127.0.0.1
    port: 6379
    database: 1
    password:

自定义序列化配置(解决fastjson反序列化失败autoType is not support问题)

/**
 * {@link RedisSerializer} FastJson Generic Impl
 * 解决fastjson反序列化失败autoType is not support问题, fastJson反序列化要求类中有默认的空参构造方法
 *
 * @author lihengming
 * @since 1.2.36
 */
public class AutoTypeGenericFastJsonRedisSerializer implements RedisSerializer<Object> {

  private final static ParserConfig defaultRedisConfig = new ParserConfig();

  static {
    defaultRedisConfig.setAutoTypeSupport(true);
    defaultRedisConfig.addAccept("com.example.demo");
  }

  @Override
  public byte[] serialize(Object object) throws SerializationException {
    if (object == null) {
      return new byte[0];
    }
    try {
      return JSON.toJSONBytes(object, SerializerFeature.WriteClassName);
    } catch (Exception ex) {
      throw new SerializationException("Could not serialize: " + ex.getMessage(), ex);
    }
  }

  @Override
  public Object deserialize(byte[] bytes) throws SerializationException {
    if (bytes == null || bytes.length == 0) {
      return null;
    }
    try {
      return JSON.parseObject(new String(bytes, IOUtils.UTF8), Object.class, defaultRedisConfig);
    } catch (Exception ex) {
      throw new SerializationException("Could not deserialize: " + ex.getMessage(), ex);
    }
  }
}

redis配置(设置序列化, 定义redisTemplate)

/**
 * BizConfiguration
 *
 * @author Shenzhen Greatonce Co Ltd
 * @author ginta
 * @version 2019/3/7
 */
@EnableCaching
@Configuration
public class BizConfiguration {

  @Bean
  AutoTypeGenericFastJsonRedisSerializer fastJsonRedisSerializer() {
    return new AutoTypeGenericFastJsonRedisSerializer();
  }

  @Bean
  public RedisCacheConfiguration redisCacheConfiguration() {
    return RedisCacheConfiguration.defaultCacheConfig()
        .serializeKeysWith(RedisSerializationContext.SerializationPair.fromSerializer(new StringRedisSerializer()))
        .serializeValuesWith(
            RedisSerializationContext.SerializationPair.fromSerializer(fastJsonRedisSerializer()))
        .entryTtl(Duration.ofDays(30));
  }

  @Bean
  public RedisTemplate<String, Object> redisTemplate(
      RedisConnectionFactory redisConnectionFactory) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory(redisConnectionFactory);
    template.setEnableDefaultSerializer(false);
    template.setKeySerializer(new StringRedisSerializer());

    template.setValueSerializer(fastJsonRedisSerializer());
    template.setHashKeySerializer(fastJsonRedisSerializer());
    template.setHashValueSerializer(fastJsonRedisSerializer());
    return template;
  }
}

使用API操作基本redis基本数据类型

ValueOperations:字符串类型操作 ListOperations:列表类型操作 SetOperations:集合类型操作 ZSetOperations:有序集合类型操作 HashOperations:散列操作 示例:

@Autowired
RedisTemplate<String, Object> redisTemplate;

存:
redisTemplate.opsForHash()
          .put(LubanConstants.LUBAN_AI_GENERATE_CALLBACK_PREFIX, image.getLubanImageId(), image);

取:
Image image = redisTemplate.<String, Image>opsForHash()
          .get(LubanConstants.LUBAN_MICRO_EDIT_CALLBACK_PREFIX, targetId);

SpringCache+Redis

配置

如上, 加上自定义非null注解即可 自定义缓存非NULL注解

/**
 * 缓存非NULL注解.
 *
 * @author ginta
 * @author Shenzhen Greatonce Co Ltd
 * @version 2018-07-19
 */

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Cacheable
public @interface CacheableNotNull {

  @AliasFor(annotation = Cacheable.class)
  String[] value() default {};

  @AliasFor(annotation = Cacheable.class)
  String[] cacheNames() default {};

  @AliasFor(annotation = Cacheable.class)
  String key() default "";

  @AliasFor(annotation = Cacheable.class)
  String keyGenerator() default "";

  @AliasFor(annotation = Cacheable.class)
  String cacheManager() default "";

  @AliasFor(annotation = Cacheable.class)
  String cacheResolver() default "";

  @AliasFor(annotation = Cacheable.class)
  String condition() default "";

  @AliasFor(annotation = Cacheable.class)
  String unless() default "#result == null";

  @AliasFor(annotation = Cacheable.class)
  boolean sync() default false;
}

相关概念

Spring Cache是作用在方法上的,其核心思想是这样的:当我们在调用一个缓存方法时会把该方法参数和返回结果作为一个键值对存放在缓存中,等到下次利用同样的参数来调用该方法时将不再执行该方法,而是直接从缓存中获取结果进行返回。

相关注解

@Cacheable 触发缓存入口 (方法,类)

需要注意的是当一个支持缓存的方法在对象内部被调用时是不会触发缓存功能的。 @Cacheable可以指定三个属性,value(指定Cache名称)、key(支持SpringEL表达式,没有指定则为默认策略生成key)和condition(指定发生的条件)。

value(指定Cache名称)

@Cacheable("cache1")//Cache是发生在cache1上的
 @Cacheable({"cache1", "cache2"})//Cache是发生在cache1和cache2上的
   public User find(Integer id) {
      returnnull;
   }
   
  key(支持SpringEL表达式,没有指定则为默认策略生成key)
   
   springEL表达式(“#参数名”或者“#p参数index”)
    @Cacheable(value="users", key="#id")
   public User find(Integer id) {
      returnnull;
   }

   @Cacheable(value="users", key="#p0")
   public User find(Integer id) {
      returnnull;
   }

   @Cacheable(value="users", key="#user.id")
   public User find(User user) {
     returnnull;
   }

   @Cacheable(value="users", key="#p0.id")
   public User find(User user) {
      returnnull;
   }
   
   condition(指定发生的条件)
   @Cacheable(value={"users"}, key="#user.id", condition="#user.id%2==0")
   public User find(User user) {
      System.out.println("find user by user " + user);
      return user;
   }

除了上述使用方法参数作为key之外,Spring还为我们提供了一个root对象可以用来生成key。通过该root对象我们可以获取到以下信息。 1744962511399

@CacahePut 更新缓存

与@Cacheable不同的是使用@CachePut标注的方法在执行前不会去检查缓存中是否存在之前执行过的结果,而是每次都会执行该方法,并将执行结果以键值对的形式存入指定的缓存中。 可以指定的属性跟@Cacheable是一样的

@CacheEvict 触发移除缓存(方法,类)

可以指定的属性有value、key、condition、allEntries(allEntries是boolean类型,表示是否需要清除缓存中的所有元素。默认为false)和beforeInvocation( 清除操作默认是在对应方法成功执行之后触发的,即方法如果因为抛出异常而未能成功返回时也不会触发清除操作。使用beforeInvocation可以改变触发清除操作的时间,当我们指定该属性值为true时,Spring会在调用该方法之前清除缓存中的指定元素。)

@Caching 将多种缓存操作分组

Caching注解可以让我们在一个方法或者类上同时指定多个Spring Cache相关的注解。其拥有三个属性:cacheable、put和evict,分别用于指定@Cacheable、@CachePut和@CacheEvict。

@Caching(cacheable = @Cacheable("users"), evict = { @CacheEvict("cache2"),
         @CacheEvict(value = "cache3", allEntries = true) })
   public User find(Integer id) {
      returnnull;
   }

@CacheConfig 类级别的缓存注解,允许共享缓存名称

使用自定义注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Cacheable(value="users")
public @interface MyCacheable {
}

那么在我们需要缓存的方法上使用@MyCacheable进行标注也可以达到同样的效果。
@MyCacheable
public User findById(Integer id) {
  System.out.println("find user by id: " + id);
  User user = new User();
 user.setId(id);
  user.setName("Name" + id);
  return user;
}

使用示例

添加缓存:

租户
  租户信息:TENANT_${tenantId}
  租户OSS设置:OSS_SETTING_${tenantId}
店铺
  STORE_${tanantId}_${storeId}
  STORE_${tanantId}_UID_${userId}
品牌
  BRAND_${tanantId}_UID_${userId}
标签
  LABEL_${tanantId}_${labelId}
权限
  PRIVILEGE_${tanantId}_UID_${userId}_${privilegeType}
菜单
  NAVIGATION
模板库
  TEMPLATE_LIB_ROOT_${tanantId}_UID_${userId}
图片库
  IMAGE_LIB_ROOT_${tanantId}_UID_${userId}

注解使用:

/**
 * 租户.
 *
 * @author 深圳市巨益软件有限公司
 * @version 1.0
 */
@Service
public class TenantServiceImpl extends AbstractService<Tenant, TenantQuery> implements TenantService {

  private static final String CACHE_NAME = "TENANT";

  @Autowired
  TenantDao dao;

  @Override
  protected QueryDao<Tenant, TenantQuery> getDAO() {
    return this.dao;
  }

  /**
   * 启用.
   */
  @Override
  public int enable(Tenant entity) {
    entity.setEnable(true);
    return dao.update(entity);
  }

  /**
   * 禁用.
   */
  @Override
  public int disable(Tenant entity) {
    entity.setEnable(false);
    return dao.update(entity);
  }

  @Override
  @CacheableNotNull(value = CACHE_NAME, key = "'CODE_'+#code")
  public Tenant getByCode(String code) {
    Tenant eg = new Tenant();
    eg.setEnable(true);
    eg.setTenantCode(code);
    return getByExample(eg);
  }

  @Override
  @CacheableNotNull(value = CACHE_NAME, key = "#id")
  public Tenant getByKey(Long id) {
    return super.getByKey(id);
  }

  @Override
  @Caching(evict = {
      @CacheEvict(value = CACHE_NAME, key = "#entity.tenantId"),
      @CacheEvict(value = CACHE_NAME, key = "'CODE_'+#entity.tenantCode")
  })
  public int modify(Tenant entity) {
    return super.modify(entity);
  }

  @Override
  @Caching(evict = {
      @CacheEvict(value = CACHE_NAME, key = "#entity.tenantId"),
      @CacheEvict(value = CACHE_NAME, key = "'CODE_'+#entity.tenantCode")
  })
  public int remove(Tenant entity) {
    return super.remove(entity);
  }

}

1744962595319

Redis的发布订阅(pub/sub)

官方文档 https://spring.io/guides/gs/messaging-redis/ 玩法: 配置上创建消息监听容器, 容器中加适配器和通道, 适配器中包裹着监听的类以及方法, 另外在适配器上,加上redis的序列化 然后发送消息, 监听类监听消息并处理即可

配置

参见官网配置

package com.greatonce.tuya.service.consumer;

import com.greatonce.tuya.util.json.AutoTypeGenericFastJsonRedisSerializer;
import javax.annotation.Resource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

/**
 * ConsumerConfiguration
 *
 * @author Shenzhen Greatonce Co Ltd
 * @author ginta
 * @version 2019/4/28
 */
@Configuration
public class ConsumerConfiguration {

  private AutoTypeGenericFastJsonRedisSerializer fastJsonRedisSerializer;

  public ConsumerConfiguration(AutoTypeGenericFastJsonRedisSerializer fastJsonRedisSerializer) {
    this.fastJsonRedisSerializer = fastJsonRedisSerializer;
  }

  /**
   * 创建一个监听消息容器:
   * 1.创建连接
   * 2.添加MessageListener,同时声明通道
   */
  @Bean
  RedisMessageListenerContainer container(RedisConnectionFactory connectionFactory,
      MessageListenerAdapter listenerAdapter) {

    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    container.addMessageListener(listenerAdapter, new PatternTopic("chat"));
    return container;
  }

  /**
   * 消息消费类包裹在消息监听适配器中:
   * 声明监听类以及方法
   */
  @Bean
  MessageListenerAdapter listenerAdapter(StoreMessageListener receiver) {
    MessageListenerAdapter adapter = new MessageListenerAdapter(receiver, "onStoreModified");
    adapter.setSerializer(fastJsonRedisSerializer);
    return adapter;
  }
}

为多通道, 改进后的配置类

package com.greatonce.tuya.service.consumer;

import com.greatonce.tuya.util.json.AutoTypeGenericFastJsonRedisSerializer;
import javax.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.listener.PatternTopic;
import org.springframework.data.redis.listener.RedisMessageListenerContainer;
import org.springframework.data.redis.listener.adapter.MessageListenerAdapter;

/**
 * ConsumerConfiguration
 *
 * @author Shenzhen Greatonce Co Ltd
 * @author ginta
 * @version 2019/4/28
 */
@Configuration
public class ConsumerConfiguration {

  private AutoTypeGenericFastJsonRedisSerializer fastJsonRedisSerializer;

  public ConsumerConfiguration(AutoTypeGenericFastJsonRedisSerializer fastJsonRedisSerializer) {
    this.fastJsonRedisSerializer = fastJsonRedisSerializer;
  }

  @Resource
  StoreMessageListener storeMessageListener;
  @Resource
  MessageListenerAdapter storeModifiedAdapter;


  @Bean
  RedisMessageListenerContainer redisMessageListenerContainer(RedisConnectionFactory connectionFactory) {
    RedisMessageListenerContainer container = new RedisMessageListenerContainer();
    container.setConnectionFactory(connectionFactory);
    addMessageListener(container, storeModifiedAdapter, "STORE_MODIFIED");
    return container;
  }


  @Bean
  MessageListenerAdapter storeModifiedAdapter() {
    return createMessageListenerAdapter(storeMessageListener, "onStoreModified");
  }

  private void addMessageListener(RedisMessageListenerContainer container, MessageListenerAdapter adapter,
      String topic) {
    container.addMessageListener(adapter, new PatternTopic(topic));
  }

  private MessageListenerAdapter createMessageListenerAdapter(Object delegate, String defaultListenerMethod) {
    MessageListenerAdapter adapter = new MessageListenerAdapter(delegate, defaultListenerMethod);
    adapter.setSerializer(fastJsonRedisSerializer);
    return adapter;
  }
}

使用

1.消息监听类

/**
 * StoreMessageListener
 *
 * @author Shenzhen Greatonce Co Ltd
 * @author ginta
 * @version 2019/5/20
 */
@Component
public class StoreMessageListener {

  public void onStoreModified(Store message) {
    System.out.println("StoreMessageListener------->" + message);
  }
}

2.发送消息

@Override
public int modify(Store entity) {
  int count = super.modify(entity);
  redisTemplate.convertAndSend("chat", entity);
  return count;
}

Redis内存模型

参考: https://jue