【Spring学习】Spring Data Redis:RedisTemplate、Repository、Cache注解
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 {
@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操作
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时,方法执行之前会清除指定的缓存,这样不论方法执行成功还是失败都会清除缓存 |