Spring Boot集成Redis
在Spring Boot项目中使用Redis时,需要如下几个步骤对Redis进行整合。
1. 加入Redis和Jedis客户端依赖。
<!-- 配置Redis依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<artifactId>lettuce-core</artifactId>
<groupId>io.lettuce</groupId>
</exclusion>
</exclusions>
</dependency>
<!-- 引入Jedis客户端依赖 -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
2. 在Spring Boot中配置Redis。在application.properties配置文件中进行如下的配置:
# Spring Boot整合Redis
# 配置连接池属性
spring.redis.jedis.pool.min-idle=5
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000
# 配置Redis服务器属性
spring.redis.port=6379
spring.redis.host=[REDIS_SERVER_IP]
# 在Redis没开启密码AUTH时无需设置
#spring.redis.password=[REDIS_SERVER_AUTH]
# Redis连接超时时间(毫秒)
spring.redis.timeout=1000
上面的配置文件中配置的Redis的连接池属性,用以连接Redis数据库。这样Spring Boot在自动装配时就会读取这里的配置来生成有关的Redis操作对象,例如RedisConnectionFactory、RedisTemplate、StringRedisTemplate常用的操作对象。其中RedisConnectionFactory对象的作用为:RedisConnectionFactory为RedisConnection对象创建的工厂方法,而RedisConnection对象是对Redis底层接口的封装,例如使用Jedis作为客户端时,将会提供RedisConnection接口的实现类JedisConnection对象用于连接Redis服务。
3. 配置Redis的序列化器。
RedisTemplate会默认使用JdkSerializationRedisSerializer进行序列化键值,这样Redis会将存入的对象序列化为一个特殊的字符串。为方便使用String类型和对象类型的的存储,下面对Redis中的序列化器进行一定的设置。创建如下RedisConfiguration配置类:
/**
* SpringBoot中整合Redis时,设置redisTemplate中的序列化器
*
* @author yitian
*/
@Configuration
public class SpringRedisConfiguration {
@Autowired
private RedisTemplate redisTemplate;
/**
* 利用Bean生命周期使用PostConstruct注解自定义后初始化方法
*/
@PostConstruct
public void init() {
initRedisTemplate();
}
/**
* 设置RedisTemplate的序列化器
*/
private void initRedisTemplate() {
RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
// 将Key和其散列表数据类型的filed都修改为使用StringRedisSerializer进行序列化
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
}
}
这里使用Spring中Bean的生命周期特性中的@PostConstruct注解,定义后初始化方法。将RedisTemplate中的key序列化器和散列表的field都改为StringRedisSerializer, 这样在Redis服务器中得到的key和散列中的field就都是String类型的了。
以上操作即完成了Spring Boot项目中的Redis集成。
使用RedisTemplate进操作基本数据结构示例
通过以上的过程可以将Redis方便的集成到Spring Boot项目中,下面对Redis中的常用数据结构的操作进行示例说明,主要的数据类型包括:字符串(String)、散列表(Hash)、列表(List)、集合(Set)和有序集合(ZSet)。具体示例如下:
String操作示例
所有的操作示例均在RedisController类中,在通过相应的请求后,在IDEA的console中输出相应的示例结果:
@Controller
@RequestMapping("/redis")
public class RedisController {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* Redis字符串String操作示例
*/
@GetMapping("/string")
@ResponseBody
public CommonResult redisStringOps() {
// 向Redis中写入数据,形式为: <key, value>
redisTemplate.opsForValue().set("key1", "value1");
redisTemplate.opsForValue().set("int_key", "1");
stringRedisTemplate.opsForValue().set("int", "1");
stringRedisTemplate.opsForValue().increment("int", 5);
System.out.println(redisTemplate.opsForValue().get("key1")); // output: value1
System.out.println(redisTemplate.opsForValue().get("int_key")); // output: 1
System.out.println(stringRedisTemplate.opsForValue().get("int")); // output: 6, 这里如果使用redisTemplate会存在NPE
// RedisTemplate不支持-1操作,所以需要获取底层的Jedis连接进行操作
Jedis jedis = (Jedis) stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection();
jedis.decr("int");
System.out.println(stringRedisTemplate.opsForValue().get("int")); // output: 5
return new CommonResult(true, "见Console输出");
}
}
Hash操作示例
/**
* Redis散列表Hash操作示例
*/
@GetMapping("/hash")
@ResponseBody
public CommonResult redisHashOps() {
// 存入一个Hash类型数据,第一个为存入Hash数据结构的整体key,后面为对应存入的值,也就是如下结构:
// Map<Key, Map<field, value>>
Map<String, Object> hash = new HashMap<>();
hash.put("field1", "value1");
hash.put("field2", "value2");
stringRedisTemplate.opsForHash().putAll("hash", hash);
System.out.println(stringRedisTemplate.opsForHash().get("hash", "field1")); // output: value1
// 向key为hash的散列表中存入单条数据
stringRedisTemplate.opsForHash().put("hash", "field3", "value3");
System.out.println(stringRedisTemplate.opsForHash().get("hash", "field3")); // output: value3
// 绑定散列表Hash进行操作,可以对一个hash进行多次操作
BoundHashOperations hashOps = stringRedisTemplate.boundHashOps("hash");
hashOps.put("field4", "value4");
hashOps.delete("field4");
// 输出名为hash散列表中的所有key和value
System.out.println(hashOps.keys()); // output: [filed, field1, field2, field3]
System.out.println(hashOps.values()); // output: [hvalue, value1, value2, value3]
// 创建另一个散列表进行操作
stringRedisTemplate.opsForHash().put("hash1", "hash1_key1", "hash1_value1");
System.out.println(stringRedisTemplate.opsForHash().get("hash1", "hash1_key1")); // output: hash1_value1
return new CommonResult(true, "见Console输出");
}
List操作示例
/**
* Redis列表操作示例
* List为链表结构,查询性能低,插入性能高
*/
@GetMapping("/list")
@ResponseBody
public CommonResult redisListOps() {
// 在开始操作前判断并删除已存在的list,否则下面的过程会对同一个list进行累计操作
deleteExistedList("list1");
deleteExistedList("list2");
// 从左插入一个链表,名为list1,顺序为,v8, v6, v4, v2
stringRedisTemplate.opsForList().leftPushAll("list1", "v2", "v4", "v6", "v8");
printRedisList("list1"); // List: list1, elements: v8, v6, v4, v2,
// 从右侧插入一个列表,名为list2,顺序为, v1, v3, v7, v9
stringRedisTemplate.opsForList().rightPushAll("list2", "v1", "v3", "v7", "v9");
printRedisList("list2"); // List: list2, elements: v1, v3, v7, v9,
// pop方法会将list中的元素弹出后删除
System.out.println(stringRedisTemplate.opsForList().leftPop("list1")); // v8
System.out.println(stringRedisTemplate.opsForList().rightPop("list1")); // v2
System.out.println(stringRedisTemplate.opsForList().leftPop("list2")); // v1
System.out.println(stringRedisTemplate.opsForList().rightPop("list2")); // v9
// 使用BoundOperations绑定名为list2的列表进行多次操作
BoundListOperations listOps = stringRedisTemplate.boundListOps("list2");
// 从list2右侧输出一个元素
Object result1 = listOps.rightPop();
System.out.println(result1); // v7
// 输出list2中index=1的元素,(List中从0开始)
Object result2 = listOps.index(1);
System.out.println(result2); // null,因为此时list2经过上述的pop操作只剩下一个元素
// 从左侧向list2中加入一个元素为v0
listOps.leftPush("v0");
// 输出List2中元素的总个数
Long size = listOps.size();
System.out.println(size); // 2
// 取出list2中【0,size-1】范围内的所有元素
List elements = listOps.range(0, size - 1);
System.out.println(elements); // [v0, v3]
// 测试range方法是否会pop出list中的元素,答案:不会,list中的元素以复制的方式进行返回
System.out.println(listOps.size());
return new CommonResult(true, "见Console输出");
}
其中使用到的一些公用方法如下:
/**
* 使用List中的range方法来获取list中的所有元素
* range方法不会像pop方法一样将元素从redis中删除
*/
private void printRedisList(String listKey) {
BoundListOperations listOps = stringRedisTemplate.boundListOps(listKey);
Long size = listOps.size();
List elemetns = listOps.range(0, size - 1);
StringBuilder sb = new StringBuilder();
sb.append("List: " + listKey + ", elements: ");
for (int i = 0; i < elemetns.size(); i++) {
sb.append(elemetns.get(i) + ", ");
}
System.out.println(sb.toString());
}
/**
* 判断list是否存在并进行删除
*/
private void deleteExistedList(String listKey) {
if (stringRedisTemplate.opsForList().size(listKey) != null) {
stringRedisTemplate.delete(listKey);
}
}
Set操作示例
/**
* Redis中无序集合操作Set,元素无序且不能重复
*/
@GetMapping("/set")
@ResponseBody
public CommonResult redisSetOps() {
// 再开始操作set之前,判断并清空set集合中的元素
emptySet("set1");
emptySet("set2");
// 分别向set1和set2集合添加元素
stringRedisTemplate.opsForSet().add("set1", "v1", "v1", "v2", "v3", "v4", "v5");
stringRedisTemplate.opsForSet().add("set2", "v2", "v4", "v6", "v8");
System.out.println(stringRedisTemplate.opsForSet().members("set1")); // [v4, v2, v3, v1, v5]
System.out.println(stringRedisTemplate.opsForSet().members("set2")); // [v6, v4, v2, v8]
// 绑定一个set1集合进行多次操作
BoundSetOperations setOps = stringRedisTemplate.boundSetOps("set1");
// 添加元素
setOps.add("v6", "v7");
// 删除集合中的元素
setOps.remove("v1", "v7");
System.out.println(setOps.members()); // [v2, v5, v3, v6, v4]
System.out.println(setOps.size()); // 5
// 求交集
Set inter = setOps.intersect("set2");
System.out.println(inter); // [v6, v4, v2]
// 求差集
Set diff = setOps.diff("set2");
System.out.println(diff); // [v5, v3]
// 测试求差集后是否会影响原set中的元素
System.out.println(setOps.members()); // [v2, v5, v3, v6, v4]
// 求差集并使用新的集合保存
setOps.diffAndStore("set2", "diff1");
printRedisList("diff1");
// 求并集
Set union = setOps.union("set2");
System.out.println(union); // [v2, v5, v3, v6, v4, v8]
// 求并集并使用新的集合保存
setOps.unionAndStore("set2", "union1");
printSetMembers("union1");
return new CommonResult(true, "见Console输出");
}
其中使用到的一些公用方法如下:
/**
* 清空已存在的set集合,避免多次操作过程中历史数据对示例操作的影响
*/
private void emptySet(String setKey) {
if (!stringRedisTemplate.opsForSet().members(setKey).isEmpty()) {
stringRedisTemplate.opsForSet().members(setKey).clear();
}
}
/**
* 打印set结合中的所有元素
*/
private void printSetMembers(String setKey) {
BoundSetOperations setOps = stringRedisTemplate.boundSetOps(setKey);
System.out.println(setOps.members());
}
ZSet操作示例
/**
* Redis中ZSet集合示例操作
* Zset集合根据score值进行排序,所以每个元素的结构为/<value, score>
*/
@GetMapping("/zset")
@ResponseBody
public CommonResult redisZsetOps() {
// 使用TypedTuple创建ZSET
Set<ZSetOperations.TypedTuple<String>> typedTuples = new HashSet<>();
for (int i = 1; i <= 9; i++) {
double score = i * 0.1;
ZSetOperations.TypedTuple<String> typedTuple = new DefaultTypedTuple<>("value" + i, score);
typedTuples.add(typedTuple);
}
stringRedisTemplate.opsForZSet().add("zset1", typedTuples);
// 输出Zset的集合大小
BoundZSetOperations zsetOps = stringRedisTemplate.boundZSetOps("zset1");
Long size = zsetOps.size();
System.out.println(size); // 8
// 输出Zset结合中的所有元素
Set members = zsetOps.range(0, size - 1);
System.out.println(members); // [value1, value2, value3, value4, value5, value6, value7, value8]
// 加入一个元素
zsetOps.add("value10", 0.26);
// 输出score在该范围中的所有元素
Set setScore = zsetOps.rangeByScore(0.2, 0.6);
System.out.println(setScore); // [value2, value10, value3, value4, value5]
// 定义值范围
RedisZSetCommands.Range range = new RedisZSetCommands.Range();
// range.gt("value3"); // 大于value3
// range.lt("value8"); // 小于value8
range.lte("value8"); // 小于等于value8
// 按值排序
Set setLex = zsetOps.rangeByLex(range);
System.out.println(setLex); // [value1, value2, value10, value3, value4, value5, value6, value7, value8]
// 删除元素
zsetOps.remove("value9", "value2");
System.out.println(zsetOps.range(0, zsetOps.size() - 1)); // [value1, value10, value3, value4, value5, value6, value7, value8]
// 得到value值对应的分数
Double score = zsetOps.score("value8");
System.out.println(score); // 0.8
// 在下标区间内,按分数排序,同时返回value和score
Set<ZSetOperations.TypedTuple<String>> rangeSet = zsetOps.rangeWithScores(1, 6);
System.out.println(rangeSet.toArray());
// 在分数区间内,按分数排序,同时返回value和score
Set<ZSetOperations.TypedTuple<String>> scoreSet = zsetOps.rangeByScoreWithScores(1, 6);
System.out.println(scoreSet); // []
// 从大到小排序,默认为从小到达排序
Set<String> reverseSet = zsetOps.reverseRange(2, 8);
System.out.println(reverseSet); // [value6, value5, value4, value3, value10, value1]
return new CommonResult(true, "见Console输出");
}