首先需要主要 ,redis 集成到spring boot 中有好多不同种类的框架, 网上好多解决的有3中,
一种: 完全注解 方式,
如:需要在主程序中用:@EnableCaching 服务层用:@Cacheable 和 @CacheEvict
@Cacheable :当redis缓存中没有对应的数据时,向数据库查询,并将查询结果新增到redis缓存;
@CacheEvict:此方法执行后,删除对应的redis缓存;
这里不详细讲解。
二种:Jedis
1:这是一个框架需要引用到的包:
<dependency>
<groupid>org.springframework.data</groupid>
<artifactid>spring-data-redis</artifactid>
<version>1.7.2.RELEASE</version>
</dependency>
<dependency>
<groupid>redis.clients</groupid>
<artifactid>jedis</artifactid>
<version>2.8.1</version>
</dependency>
2. 添加redis配置文件(非必须,可以在spring配置文件中直接配置)
redis.host=127.0.0.1
#访问端口
redis.port=6379
#注意,如果没有password,此处不设置值,但这一项要保留
redis.password=
#最大空闲数,数据库连接的最大空闲时间。超过空闲时间,数据库连接将被标记为不可用,然后被释放。设为0表示无限制。
redis.maxIdle=300
#连接池的最大数据库连接数。设为0表示无限制
redis.maxActive=600
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
redis.maxWait=1000
#在borrow一个jedis实例时,是否提前进行alidate操作;如果为true,则得到的jedis实例均是可用的;
redis.testOnBorrow=true
写的不完整,因为没有用这种方式集成redis
三种: RedisTemplate
(1)本文所采用的SpringBoot的版本如下
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.2.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
(2)加入Redis相关依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
二、application.properties中加入redis相关配置
一般现在用的比较多的是yml
spring:
redis:
host: 127.0.0.1
port: 6379
database: 0
timeout: 3000
三、写一个redis配置类
redis模板源码:这段代码开发人员不需要处理,是依赖包中的。
其实现在就可以在代码中注入RedisTemplate,为啥可以直接注入呢?先看下源码吧。下图为 RedisAutoConfiguration类中的截图,为了防止图片失效,代码也贴上 。
@Configuration
@ConditionalOnClass(RedisOperations.class)
@EnableConfigurationProperties(RedisProperties.class)
@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })
public class RedisAutoConfiguration {
@Bean
@ConditionalOnMissingBean(name = "redisTemplate")
public RedisTemplate<Object, Object> redisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
RedisTemplate<Object, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
@Bean
@ConditionalOnMissingBean
public StringRedisTemplate stringRedisTemplate(
RedisConnectionFactory redisConnectionFactory) throws UnknownHostException {
StringRedisTemplate template = new StringRedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
return template;
}
}
通过源码可以看出,SpringBoot自动帮我们在容器中生成了一个RedisTemplate和一个StringRedisTemplate。但是,这个RedisTemplate的泛型是<Object,Object>,写代码不方便,需要写好多类型转换的代码;我们需要一个泛型为<String,Object>形式的RedisTemplate。并且,这个RedisTemplate没有设置数据存在Redis时,key及value的序列化方式。
看到这个@ConditionalOnMissingBean注解后,就知道如果Spring容器中有了RedisTemplate对象了,这个自动配置的RedisTemplate不会实例化。因此我们可以直接自己写个配置类,配置RedisTemplate。
开发人员只需要添加一个配置类:
因为redis内部有一个模板,这个只是对模板重新配置。重新配置一个RedisTemplate
@Configuration
public class RedisConfig {
@Bean
public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory factory){
RedisTemplate<String,Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
GenericJackson2JsonRedisSerializer serializer = new GenericJackson2JsonRedisSerializer();
// 值采用json序列化
redisTemplate.setValueSerializer(serializer);
//使用StringRedisSerializer来序列化和反序列化redis的key值
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(serializer);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
四、写一个Redis工具类
直接用RedisTemplate操作Redis,需要很多行代码,因此直接封装好一个RedisUtils,这样写代码更方便点。这个RedisUtils交给Spring容器实例化,使用时直接注解注入。
工具类代码如下: 开发人员直接复制到自己的项目中即可。
package com.unit.mapping.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* xul
* 封装 redis
*/
@Component
@Slf4j
public class RedisUtils {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* @param key
* @return 获得值
* redis有五种数据类型 opsForValue表示是操作字符串类型
*/
public Object get(String key) {
return key == null ? null : redisTemplate.opsForValue( ).get(key);
}
//本来只可以放入string类型,但是我们配置了自动序列化所以这儿可以传入object
public boolean set(String key, Object value) {
try {
redisTemplate.opsForValue( ).set(key, value);
return true;
} catch (Exception e) {
log.error("redis set value exception:{}", e);
return false;
}
}
/**
* 原子操作
*
* @param key
* @param value
* @param expire 过期时间 秒
* @return
*/
public boolean setex(String key, Object value, long expire) {
try {
//TimeUnit.SECONDS指定类型为秒
redisTemplate.opsForValue( ).set(key, value, expire, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
log.error("redis set value and expire exception:{}", e);
return false;
}
}
/**
* 非原子操作
*
* @param key
* @param expire
* @return
*/
public boolean expire(String key, long expire) {
try {
//这儿没有ops什么的是因为每种数据类型都能设置过期时间
redisTemplate.expire(key, expire, TimeUnit.SECONDS);
return true;
} catch (Exception e) {
log.error("redis set key expire exception:{}", e);
return false;
}
}
/**
* @param key
* @return 获取key的过期时间
*/
public long ttl(String key) {
return redisTemplate.getExpire(key);
}
/**
* @param keys 删除key 可变参数
*/
public void del(String... keys) {
if (keys != null && keys.length > 0) {
redisTemplate.delete((Collection<String>) CollectionUtils.arrayToList(keys));
}
}
/**
* @param key
* @param step 传入正数 就是加多少 传入负数就是减多少
* @return
*/
public long incrBy(String key, long step) {
return redisTemplate.opsForValue( ).increment(key, step);
}
/**
* @param key
* @param value
* @return 如果该key存在就返回false 设置不成功 key不存在就返回ture设置成功
*/
public boolean setnx(String key, Object value) {
return redisTemplate.opsForValue( ).setIfAbsent(key, value);
}
/**
* 原子操作
*
* @param key
* @param value
* @param expire 在上面方法加上过期时间设置
* @return
*/
public boolean setnxAndExpire(String key, Object value, long expire) {
return redisTemplate.opsForValue( ).setIfAbsent(key, value, expire, TimeUnit.SECONDS);
}
/**
* @param key
* @param value
* @return 如果该key存在就返回之前的value 不存在就返回null
*/
public Object getAndSet(String key, Object value) {
return redisTemplate.opsForValue( ).getAndSet(key, value);
}
/**
* @param key
* @return 判断key是否存在
*/
public boolean hasKey(String key) {
return redisTemplate.hasKey(key);
}
/***list的长度**/
public long llen(String key) {
return redisTemplate.opsForList( ).size(key);
}
/**
* 获取key中index位置的值,负数就反过来数,-1为最后一个
*
* @param key
* @param index
* @return
*/
public Object lgetByIndex(String key, long index) {
try {
return redisTemplate.opsForList( ).index(key, index);
} catch (Exception e) {
log.error("redis lgetByIndex error,key:{},index:{}exception:{}", key, index, e);
return null;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lrpush(String key, Object value) {
try {
redisTemplate.opsForList( ).rightPush(key, value);
return true;
} catch (Exception e) {
log.error("redis lrpush error,key:{},value:{}exception:{}", key, value, e);
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lrpush(String key, Object value, long time) {
try {
redisTemplate.opsForList( ).rightPush(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
log.error("redis lrpush error,key:{},value:{},timeL{},exception:{}", key, value, time, e);
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @return
*/
public boolean lrpush(String key, List<Object> value) {
try {
redisTemplate.opsForList( ).rightPushAll(key, value);
return true;
} catch (Exception e) {
log.error("redis lrpush error,key:{},value:{},exception:{}", key, value, e);
return false;
}
}
/**
* 将list放入缓存
*
* @param key 键
* @param value 值
* @param time 时间(秒)
* @return
*/
public boolean lrpush(String key, List<Object> value, long time) {
try {
redisTemplate.opsForList( ).rightPushAll(key, value);
if (time > 0) {
expire(key, time);
}
return true;
} catch (Exception e) {
log.error("redis lrpush error,key:{},value:{},time:{},exception:{}", key, value, time, e);
return false;
}
}
/**
* 获取列表指定范围内的元素
*
* @param key
* @param start
* 开始位置, 0是开始位置
* @param end
* 结束位置, -1返回所有
* @return
*/
public List<Object> lRange(String key, long start, long end) {
return redisTemplate.opsForList().range(key, start, end);
}
/**
* 根据索引修改list中的某条数据
*
* @param key 键
* @param index 索引
* @param value 值
* @return
*/
public boolean lUpdateByIndex(String key, long index, Object value) {
try {
redisTemplate.opsForList( ).set(key, index, value);
return true;
} catch (Exception e) {
log.error("redis lUpdateByIndex error,key:{},index:{},value:{},exception:{}", key, index, value, e);
return false;
}
}
/**
* 移除N个值为value
*
* @param key 键
* @param count 移除多少个
* @param value 值
* @return 移除的个数
*/
public long lrem(String key, long count, Object value) {
try {
Long remove = redisTemplate.opsForList( ).remove(key, count, value);
return remove;
} catch (Exception e) {
log.error("redis lrem error,key:{},count:{},value:{},exception:{}", key, count, value, e);
return 0;
}
}
/*****hash数据类型方法 opsForHash表示是操作字符串类型*****/
/**
* @param key 健
* @param field 属性
* @param value 值
* @return
*/
public boolean hset(String key, String field, Object value) {
try {
redisTemplate.opsForHash( ).put(key, field, value);
return true;
} catch (Exception e) {
log.error("redis hset eror,key:{},field:{},value:{}", key, field, value);
return false;
}
}
/**
* @param key
* @param field
* @param value
* @param seconds(秒) 过期时间
* @return
*/
public boolean hset(String key, String field, Object value, long seconds) {
try {
redisTemplate.opsForHash( ).put(key, field, value);
expire(key, seconds);//调用通用方法设置过期时间
return true;
} catch (Exception e) {
log.error("redis hset and expire eror,key:{},field:{},value:{},exception:{}", key, field, value, e);
return false;
}
}
/**
* 获取key中field属性的值
*
* @param key
* @param field
* @return
*/
public Object hget(String key, String field) {
return redisTemplate.opsForHash( ).get(key, field);
}
/**
* 获取key中多个属性的键值对,这儿使用map来接收
*
* @param key
* @param fields
* @return
*/
public Map<String, Object> hmget(String key, String... fields) {
Map<String, Object> map = new HashMap<>( );
for (String field : fields) {
map.put(field, hget(key, field));
}
return map;
}
/**
* @param key 获得该key下的所有键值对
* @return
*/
public Map<Object, Object> hmget(String key) {
return redisTemplate.opsForHash( ).entries(key);
}
// /**
// * @param key 获得该key下的所有键值对
// * @return
// */
// @Autowired
// private JacksonUtil jacksonUtil;
// //map----json字符串---->对象
// public <T>T hmgetObject(String key,Class<T> tClass){
// Map<Object, Object> hmget = hmget(key);
// if(CollectionUtils.isEmpty(hmget)) return null;
// //查询到了 先把数据转成json字符串
// String s = jacksonUtil.writeAsString(hmget);
// //再把json字符串转回对象
// return jacksonUtil.readValue(s,tClass);
// }
/**
* @param key 键
* @param map 对应多个键值
* @return
*/
public boolean hmset(String key, Map<Object, Object> map) {
try {
redisTemplate.opsForHash( ).putAll(key, map);
return true;
} catch (Exception e) {
log.error("redis hmset eror,key:{},value:{},exception:{}", key, map, e);
return false;
}
}
// public boolean hmset(String key,Object object){
// try {
// String s = jacksonUtil.writeAsString(object);
// Map<String, String> map = jacksonUtil.readValueMap(s);
// redisTemplate.opsForHash().putAll(key, map);
// return true;
// }catch (Exception e){
// log.error("redis hmset eror,key:{},object:{},exception:{}",key,object,e);
// return false;
// }
// }
/**
* @param key 键
* @param map 对应多个键值
* @param seconds 过期时间(秒)
* @return
*/
public boolean hmset(String key, Map<String, Object> map, long seconds) {
try {
redisTemplate.opsForHash( ).putAll(key, map);
expire(key, seconds);
return true;
} catch (Exception e) {
log.error("redis hmset eror,key:{},value:{},expireTime,exception:{}", key, map, seconds, e);
return false;
}
}
/**
* 删除key中的属性
*
* @param key
* @param fields
*/
public void hdel(String key, Object... fields) {
redisTemplate.opsForHash( ).delete(key, fields);
}
/**
* 判断key中是否存在某属性
*
* @param key
* @param field
* @return
*/
public boolean hHashKey(String key, String field) {
return redisTemplate.opsForHash( ).hasKey(key, field);
}
/**
* 对key中filed的value增加多少 如果是减少就传入负数
*
* @param key
* @param field
* @param step 正数增加,负数减少
* @return
*/
public double hincr(String key, String field, double step) {
return redisTemplate.opsForHash( ).increment(key, field, step);
}
/**
* key中多少个
*
* @param key
* @return
*/
public long hlen(String key) {
return redisTemplate.opsForHash( ).size(key);
}
/******其他方法用到在增加********/
/***set集合***/
/**
* 获取key中所有元素
*
* @param key
* @return
*/
public Set<Object> sgetAll(String key) {
try {
return redisTemplate.opsForSet( ).members(key);
} catch (Exception e) {
log.error("redis sgetAll error,key:{},exception:{}", key, e);
return null;
}
}
/**
* 判断value是否在key中
*
* @param key
* @param value
* @return
*/
public boolean sexists(String key, Object value) {
try {
return redisTemplate.opsForSet( ).isMember(key, value);
} catch (Exception e) {
log.error("redis sexists error,key:{},value:{},exception:{}", key, value, e);
return false;
}
}
/**
* 插入多个元素
*
* @param key
* @param values
* @return 成功的个数
*/
public long sset(String key, Object... values) {
try {
return redisTemplate.opsForSet( ).add(key, values);
} catch (Exception e) {
log.error("redis sset error,key:{},value:{},values:{},exception:{}", key, values, e);
return 0;
}
}
/**
* 添加元素并设置过期时间 (非原子操作)
*
* @param key
* @param time
* @param values
* @return
*/
public long sset(String key, long time, Object... values) {
try {
long count = redisTemplate.opsForSet( ).add(key, values);
expire(key, time);
return count;
} catch (Exception e) {
log.error("redis sset error,key:{},value:{},values:{},exception:{}", key, values, e);
return 0;
}
}
/**
* 获取set的长度
*
* @param key
* @return
*/
public long sgetSize(String key) {
try {
return redisTemplate.opsForSet( ).size(key);
} catch (Exception e) {
log.error("redis sgetSize error,key:{},exception:{}", key, e);
return 0;
}
}
/**
* 移除值为value的
*
* @param key 键
* @param values 值 可以是多个
* @return 移除的个数
*/
public long sRemove(String key, Object... values) {
try {
Long count = redisTemplate.opsForSet( ).remove(key, values);
return count;
} catch (Exception e) {
log.error("redis sRemove error,key:{},values:{},exception:{}", key, values, e);
return 0;
}
}
/**
* 随机取count个元素 count为正数就取不重复的 负数就有可能重复
*
* @param key
* @param count
* @return
*/
public List<Object> sRandom(String key, long count) {
try {
return redisTemplate.opsForSet( ).randomMembers(key, count);
} catch (Exception e) {
log.error("redis sRandom error,key:{},count:{},exception:{}", key, count, e);
return null;
}
}
/****zset工具类***/
/**
* 添加元素
*
* @param key
* @param member
* @param score
* @return
*/
public boolean zadd(String key, Object member, double score) {
try {
return redisTemplate.opsForZSet( ).add(key, member, score);
} catch (Exception e) {
log.error("redis zadd error,key:{},value:{},score:{},exception:{}", key, member, score, e);
return false;
}
}
public Set<String> zrange(String key, int start, int end) {
try {
Set<Object> range = redisTemplate.opsForZSet( ).
range(key, start, end);
if (range == null || range.size( ) == 0) {
return null;
}
return range.stream( ).
map(o -> (String) o).collect(Collectors.toSet( ));
} catch (Exception e) {
log.error("redis zrange error,key:{},start:{},end:{},exception:{}",
key, start, end, e);
return null;
}
}
}
五、 在server层的使用
1:根据key值查询redis,这里是保存的对象为:CityInfo,所以取出来的值需要转换为对象。默认取出来的是string
//先查询redis信息
Object str = redisUtils.get(RedisKeyConstant.Mei_Mapping_CityInfo);
List<CityInfo> allInfo = new ArrayList<>();
if (str != null) {
allInfo = JSON.parseArray(str.toString(), CityInfo.class);
if (allInfo != null && allInfo.size() > 0) {
return allInfo;
}
}
2:根据key值保存数据,
这里保存其实没有那么多代码,只需要下面一行代码即可。
redisUtils.setex(RedisKeyConstant.Mei_Mapping_CityInfo, JSONArray.toJSONString(lists), 30 * 60);
// 如果redis 获取的数据是空 ,再查询数据库
List<CityInfo> lists = new ArrayList<>();
if (allInfo == null || allInfo.size() <= 0) {
lists = this.cityInfoDao.queryAllByLimit(offset, limit);
redisUtils.setex(RedisKeyConstant.Mei_Mapping_CityInfo, JSONArray.toJSONString(lists), 30 * 60);
}
六、小结
整合其实不麻烦,网上好多博文都有。注意设置下key和value的序列化方式,不然存到Redis的中数据看起来像乱码一下。
另外如果有异常:
Unsatisfied dependency expressed through method 'redisConnectionFactory' parameter 1; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'lettuceClientResources' defined in class path resource
提示'redisConnectionFactory或者 RedisTemplate 没有注入的情况。这种情况一般是pom文件的引用问题。可以把maven清空一下,再重新下载。
用:mvn clean compile -U