【Spring学习】Spring Data Redis:RedisTemplate、Repository、Cache注解

时间:2024-02-18 21:51:20

1,spring-data-redis官网

1)特点

  1. 提供了对不同Redis客户端的整合(Lettuce和Jedis)
  2. 提供了RedisTemplate统一API来操作Redis
  3. 支持Redis的发布订阅模型
  4. 支持Redis哨兵和Redis集群
  5. 支持基于Lettuce的响应式编程
  6. 支持基于JDK、JSON、字符串、Spring独享的数据序列化及反序列化
  7. 支持基于Redis的JDKCollection实现

2,RedisTemplate

SpringDataRedis中提供了RedisTemplate工具类,其中封装了各种对Redis的操作

1)常用API

api 说明
redisTemplate.opsForValue(); 操作字符串
redisTemplate.opsForHash(); 操作hash
redisTemplate.opsForList(); 操作list
redisTemplate.opsForSet(); 操作set
redisTemplate.opsForZSet(); 操作有序set
redisTemplate.expire(key, 60 * 10000 * 30, TimeUnit.MILLISECONDS); 设置过期时间

1>API:String

redisTemplate.opsForValue().set("name","tom")
说明 api 备注
添加单值 .set(key,value)
获取单值 .get(key)
添加单值并返回这个值是否已经存在 .setIfAbsent(key, value)
添加单值并返回旧值 .getAndSet(key, value)
批量添加 Map<String,String> maps;
.multiSet(maps)
批量获取 List<String> keys;
.multiGet(keys)
数值型+1 .increment(key,1)
设置过期时间 set(key, value, timeout, TimeUnit.SECONDS) 过期返回null
字符串追加 .append(key,"Hello");

2>API:List数据

template.opsForList().range("list",0,-1)
说明 api 备注
单个插入 Long leftPush(key, value); 返回操作后的列表的长度
批量插入 Long leftPushAll(K key, V… values); 返回操作后的列表的长度;
values可以是String[]List<Object>
查看 .range(key,0,-1) 从左往右:0,1,2;
从右往左:-1,-2,-3;
可做分页
弹出最左边的元素 .leftPop("list") 弹出之后该值在列表中将不复存在
修改 set(key, index, value)
key存在则插入 Long rightPushIfPresent(K key, V value); 返回操作后的列表的长度
求subList .trim(key,1,-1)
移除元素 Long remove(key, long count, Object value); count> 0:删除从左到右共count个等于value的元素。
count <0:删除等于从右到左共count个等于value的元素。
count = 0:删除等于value的所有元素。
求长度 .size(key)

3>API:Hash操作

一个key1对应一个Map,map中每个value中@class后面对应的值为类信息。
在这里插入图片描述

template.opsForHash().put("redisHash","name","tom");
说明 api 备注
单插入 .put(redisKey,hashKey, value)
批量插入 Map<String,Object> map
.putAll(key, map)
查单数据 .get(redisKey,hashKey)
查所有数据 .entries(redisHash)
查key是否存在 Boolean hasKey(redisKey, Object hashKey);
批量获取Hash值 List multiGet(redisKey, List<Object> kes);
获取key集合 Set keys(redisKey)
批量删除 Long delete(redisKey, Object… hashKeys)
数值型value + 5 increment(redisKey, hashKey, 5) 返回操作后的value值
hashkey不存在时设置value Boolean putIfAbsent(redisKey,hashKey, value) 存在返回true,不存在返回true

遍历:

Cursor<Map.Entry<Object, Object>> curosr = template.opsForHash().scan("redisHash", ScanOptions.ScanOptions.NONE);
        while(curosr.hasNext()){
            Map.Entry<Object, Object> entry = curosr.next();
            System.out.println(entry.getKey()+":"+entry.getValue());
        }

4>API:Set数据

template.opsForSet().add(k,v)
说明 api 备注
添加 Long add(key, V… values); values可以是:String[]
查看所有 .members(key)
查询长度 .size(key)
查询元素是否存在 Boolean isMember(key, Object o);
批量删除 Long remove(key, Object… values); values可以是:String[]
随机移除 V pop(K key);
将元素value 从 sourcekey所在集合移动到 destKey所在集合 Boolean move(sourcekey, V value, destKey) 移动后sourcekey集合再没有value元素,destKey集合去重。
求两个集合的交集 Set intersect(K key, K otherKey);
求多个无序集合的交集 Set intersect(K key, Collection otherKeys);
求多个无序集合的并集 Set union(K key, Collection otherKeys);

遍历:

Cursor<Object> curosr = template.opsForSet().scan("setTest", ScanOptions.NONE);
        while(curosr.hasNext()){
            System.out.println(curosr.next());
        }

5>API:ZSet集合

有序的Set集合,排序依据是Score。

template.opsForZSet().add("zset1","zset-1",1.0)
说明 api 备注
添加单个元素 Boolean add(k, v, double score) 返回元素是否已存在
批量添加元素 Long add(k, Set<TypedTuple> tuples) 举例:见下文1.
批量删除 Long remove(K key, Object… values);
排序按分数值asc,返回成员o的排名 Long rank(key, Object o); 排名从0开始
排序按分数值desc,返回成员o的排名 Long reverseRank(key, Object o); 排名从0开始
按区间查询,按分数值asc Set range(key, 0, -1);
增加元素的score值,并返回增加后的值 Double incrementScore(K key, V value, double delta);
  1. 批量添加元素
ZSetOperations.TypedTuple<Object> objectTypedTuple1 = new DefaultTypedTuple<Object>("zset-5",9.6);
        ZSetOperations.TypedTuple<Object> objectTypedTuple2 = new DefaultTypedTuple<Object>("zset-6",9.9);
        Set<ZSetOperations.TypedTuple<Object>> tuples = new HashSet<ZSetOperations.TypedTuple<Object>>();
        tuples.add(objectTypedTuple1);
        tuples.add(objectTypedTuple2);
        System.out.println(template.opsForZSet().add("zset1",tuples));
        System.out.println(template.opsForZSet().range("zset1",0,-1));
  1. 遍历
Cursor<ZSetOperations.TypedTuple<Object>> cursor = template.opsForZSet().scan("zzset1", ScanOptions.NONE);
        while (cursor.hasNext()){
            ZSetOperations.TypedTuple<Object> item = cursor.next();
            System.out.println(item.getValue() + ":" + item.getScore());
        }

2)使用

1>依赖

<!--        Redis依赖-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
<!--        连接池依赖-->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-pool2</artifactId>
        </dependency>

2>配置文件

spring:
  redis:
    host: 127.0.0.1  # Redis服务器地址
    port: 6379       # Redis服务器连接端口 
    timeout:0        # 连接超时时间(毫秒)
#   database: 0      # Redis数据库索引(默认为0)
#   password:        # Redis服务器连接密码(默认为空)
    lettuce:         # 使用的是lettuce连接池
      pool:
        max-active: 8 # 连接池最大连接数(使用负值表示没有限制)
        max-idle: 8   # 连接池中的最大空闲连接
        min-idle: 0   # 连接池中的最小空闲连接
        max-wait: 100 # 连接池最大阻塞等待时间(使用负值表示没有限制)

1>序列化配置

RedisTemplate默认采用JDK的序列化工具,序列化为字节形式,在redis中可读性很差。
修改默认的序列化方式为jackson:

@Configuration
public class RedisConfig {
    @Bean     //RedisConnectionFactory不需要我们创建Spring会帮助我们创建
    public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory connectionFactory){
//        1.创建RedisTemplate对象
          RedisTemplate<String,Object> template = new RedisTemplate<>();
//        2.设置连接工厂
          template.setConnectionFactory(connectionFactory);
//        3.创建JSON序列化工具
          GenericJackson2JsonRedisSerializer jsonRedisSerializer = new GenericJackson2JsonRedisSerializer();
//        4.设置Key的序列化
          template.setKeySerializer(RedisSerializer.string());
          template.setHashKeySerializer(RedisSerializer.string());
//        5.设置Value的序列化   jsonRedisSerializer使我们第三步new出来的
          template.setValueSerializer(jsonRedisSerializer);
          template.setHashValueSerializer(jsonRedisSerializer);
//        6.返回
        return template;
        
    }
}

但是json序列号可能导致一些其他的问题:JSON序列化器会将类的class类型写入到JSON结果中并存入Redis,会带来额外的内存开销。
为了节省内存空间,我们并不会使用JSON序列化器来处理value,而是统一使用String序列化器,要求只能存储String类型的key哈value,当要存储Java对象时,手动完成对象的序列化和反序列化。

4>java实现

public class RedisUtil {
    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 批量删除对应的value
     * 
     * @param keys
     */
    public void remove(final String... keys) {
        for (String key : keys) {
            remove(key);
        }
    }

    /**
     * 批量删除key
     * 
     * @param pattern
     */
    public void removePattern(final String pattern) {
        Set<Serializable> keys = redisTemplate.keys(pattern);
        if (keys.size() > 0)
            redisTemplate.delete(keys);
    }

    public void remove(final String key) {
        if (exists(key)) {
            redisTemplate.delete(key);
        }
    }
    
    public boolean exists(final String key) {
        return redisTemplate.hasKey(key);
    }

    public String get(final String key) {
        Object result = null;
        ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
        result = operations.get(key);
        if (result == null) {
            return null;
        }
        return result.toString();
    }
    
    public boolean set(final String key, Object value) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public boolean set(final String key, Object value, Long expireTime) {
        boolean result = false;
        try {
            ValueOperations<Serializable, Object> operations = redisTemplate.opsForValue();
            operations.set(key, value);
            redisTemplate.expire(key, expireTime, TimeUnit.SECONDS);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public boolean hmset(String key, Map<String, String> value) {
        boolean result = false;
        try {
            redisTemplate.opsForHash().putAll(key, value);
            result = true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

    public Map<String, String> hmget(String key) {
        Map<String, String> result = null;
        try {
            result = redisTemplate.opsForHash().entries(key);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }
}

3)StringRedisTemplate

key和value的序列化方式默认就是String方式,省去了我们自定义RedisTemplate的过程。

    @Autowired
    private StringRedisTemplate stringRedisTemplate;
//  JSON工具
    private static final ObjectMapper mapper = new ObjectMapper();
 
    @Test
    void testStringTemplate() throws JsonProcessingException {
//      准备对象
        User user = new User("abc", 18);
//      手动序列化
        String json = mapper.writeValueAsString(user);
//      写入一条数据到Redis
        stringRedisTemplate.opsForValue().set("user:200",json);
 
//      读取数据
        String s = stringRedisTemplate.opsForValue().get("user:200");
//      反序列化
        User user1 = mapper.readValue(s,User.class);
        System.out.println(user1);
    }

3,Redis数据序列化

4,Repository操作

类似jpa操作,只要Redis服务器版本在2.8.0以上,不用事务,就可以使用Repository做各种操作。
注意:Repository和jpa的Repository是同一个,意味着jpa支持的api在redis操作中通用。

1)注解

注解 说明 属性 对比jpa
@RedisHash 用来定义实体。 value:定义了不同类型对象存储时使用的前缀,也叫做键空间,默认是全限定类名;
timeToLive定义缓存的秒数;
类似@Entity
@Id 定义对象的标识符 类似@Id
@Indexed 定义二级索引,加在属性上可以将该属性定义为查询用的索引
@Reference 缓存对象引用,一般引用的对象也会被展开存储在当前对象中,添加了该注解后会直接存储该对象在Redis中的引用

2)使用

  1. 不需要添加依赖。
    spring-boot自动添加了@EnableRedisRepositories注解。
  2. 实体
@RedisHash(value="menu",timeToLive=60)
public class RedisMenuItem implements Serializable{
    @Id
    privste Long id;
    @Indexed
    private String name;
    private Size size;
    private Money price;
}
  1. 定义Repository接口
public interface RedisMenuRepository extends CrudRepository<RedisMenuItem, Long>{
    List<RedisMenuItem> findByName(String name);
}
  1. 查询
List<MenuItem> itemList = menuRepository.findAll();

menuRepository.save(menuItem);

5,Spring Cache

Spring 3.1 引入了对 Cache 的支持,使用使用 JCache(JSR-107)注解简化开发。
注意:可支持幂等操作的接口才可以使用缓存注解,因为缓存和参数无关,即:不管什么参数,返回值一样。

1)org.springframework.cache.Cache接口

  1. 包含了缓存的各种操作集合;
  2. 提供了各种xxxCache 的实现,比如:RedisCache
    在这里插入图片描述

2)org.springframework.cache.CacheManager接口

  1. 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache。这些 Cache 存在于 CacheManager 的上下文中。
  2. 提供了各种xxxCacheManager 的实现,比如RedisCacheManager。

3)相关注解

1>@EnableCaching

  1. 开启基于注解的缓存;
  2. 作用在缓存配置类上或者SpringBoot 的主启动类上;

2>@Cacheable

缓存注解。

使用注意:

  1. 基于AOP去实现的,所以必须通过IOC对象去调用。
  2. 要缓存的 Java 对象必须实现 Serializable 接口。
@Cacheable(
            cacheNames = "usersBySpEL",
            //key通过变量拼接
            key="#root.methodName + '[' + #id + ']'",
            //id大于1才缓存。可缺省
            condition = "#id > 1",
            //当id大于10时,条件为true,方法返回值不会被缓存。可缺省
            unless = "#id > 10")
    public User getUserBySpEL(Integer id) {
    }
    
@Cacheable(value = {"menuById"}, key = "'id-' + #menu.id")
    public Menu findById(Menu menu) {
        return menu;
    }
常用属性 说明 备注 代码示例
cacheNames/value 缓存名称,用来划分不同的缓存区,避免相同key值互相影响。 可以是单值、数组;
在redis中相当于key的一级目录,支持:拼接多层目录
cacheNames = "users"
cacheNames = {"users","account"}
key 缓存数据时使用的 key,默认是方法参数。
可以使用 spEL 表达式来编写
keyGenerator key 的生成器,统一管理key。 key 和 keyGenerator 二选一使用,同时使用会导致异常。 keyGenerator = "myKeyGenerator"<