1,spring-data-redis官网
1)特点
- 提供了对不同Redis客户端的整合(Lettuce和Jedis)
- 提供了RedisTemplate统一API来操作Redis
- 支持Redis的发布订阅模型
- 支持Redis哨兵和Redis集群
- 支持基于Lettuce的响应式编程
- 支持基于JDK、JSON、字符串、Spring独享的数据序列化及反序列化
- 支持基于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); |
- 批量添加元素
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));
- 遍历
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 {@Autowiredprivate 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的过程。
@Autowiredprivate StringRedisTemplate stringRedisTemplate;
// JSON工具private static final ObjectMapper mapper = new ObjectMapper();@Testvoid testStringTemplate() throws JsonProcessingException {
// 准备对象User user = new User("abc", 18);
// 手动序列化String json = mapper.writeValueAsString(user);
// 写入一条数据到RedisstringRedisTemplate.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)使用
- 不需要添加依赖。
spring-boot自动添加了@EnableRedisRepositories注解。 - 实体
@RedisHash(value="menu",timeToLive=60)
public class RedisMenuItem implements Serializable{@Idprivste Long id;@Indexedprivate String name;private Size size;private Money price;
}
- 定义Repository接口
public interface RedisMenuRepository extends CrudRepository<RedisMenuItem, Long>{List<RedisMenuItem> findByName(String name);
}
- 查询
List<MenuItem> itemList = menuRepository.findAll();menuRepository.save(menuItem);
5,Spring Cache
Spring 3.1 引入了对 Cache 的支持,使用使用 JCache(JSR-107)注解简化开发。
注意:可支持幂等操作的接口才可以使用缓存注解,因为缓存和参数无关,即:不管什么参数,返回值一样。
1)org.springframework.cache.Cache接口
- 包含了缓存的各种操作集合;
- 提供了各种xxxCache 的实现,比如:RedisCache
2)org.springframework.cache.CacheManager接口
- 定义了创建、配置、获取、管理和控制多个唯一命名的 Cache。这些 Cache 存在于 CacheManager 的上下文中。
- 提供了各种xxxCacheManager 的实现,比如RedisCacheManager。
3)相关注解
1>@EnableCaching
- 开启基于注解的缓存;
- 作用在缓存配置类上或者SpringBoot 的主启动类上;
2>@Cacheable
缓存注解。
使用注意:
- 基于AOP去实现的,所以必须通过IOC对象去调用。
- 要缓存的 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" |
cacheManager | 指定缓存管理器,从哪个缓存管理器里面获取缓存 | ||
condition | 可以用来指定符合条件的情况下才缓存 | ||
unless | 否定缓存。 | 当 unless 指定的条件为 true ,方法的返回值就不会被缓存 | 通过 #result 获取方法结果进行判断。 |
sync | 是否使用异步模式。 | 默认是方法执行完,以同步的方式将方法返回的结果存在缓存中 |
spEL常用元数据:
说明 | 示例 | 备注 |
---|---|---|
#root.methodName | 当前被调用的方法名 | |
#root.method.name | 当前被调用的方法 | |
#root.target | 当前被调用的目标对象 | |
#root.targetClass | 当前被调用的目标对象类 | |
#root.args[0] | 当前被调用的方法的参数列表 | |
#root.cacheds[0].name | 当前方法调用使用的缓存区列表 | |
#参数名 或 #p0 或 #a0 | 方法的参数名; 0代表参数的索引 | |
#result | 方法执行后的返回值 | 如果没有执行则没有内容 |
3>@CachePut
主要针对配置,能够根据方法的请求参数对其结果进行缓存。
- 区别于 @Cacheable,它每次都会触发真实方法的调用,可以保证缓存的一致性。
- 属性与 @Cacheable 类同。
4>@CacheEvict
根据一定的条件对缓存进行清空。
- 标记在类上时表示其中所有方法的执行都会触发缓存的清除操作;
常用属性 | 说明 | 备注 | 代码示例 |
---|---|---|---|
value | |||
key | |||
condition | |||
allEntries | 为true时,清除value属性值中的所有缓存;默认为false,可以指定清除value属性值下具体某个key的缓存 | ||
beforeInvocation | 1. 默认是false,即在方法执行成功后触发删除缓存的操作; 2.如果方法抛出异常未能成功返回,不会触发删除缓存的操作 3.当改为true时,方法执行之前会清除指定的缓存,这样不论方法执行成功还是失败都会清除缓存 |
4)缓存实现
上文中基于注解的缓存接口,有一层CacheMananger抽象,其中用ConcurrentHashMap维护了多个Cache,通过cacheNames指定了哪个Cache。