<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
<exclusions>
<exclusion>
<groupId>io.lettucegroupId>
<artifactId>lettuce-coreartifactId>
exclusion>
exclusions>
dependency>
<dependency>
<groupId>redis.clientsgroupId>
<artifactId>jedisartifactId>
dependency>
RedisConfig
package com.lay.redis.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.RedisPassword;
import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
import org.springframework.data.redis.connection.jedis.JedisClientConfiguration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import redis.clients.jedis.JedisPoolConfig;
@Configuration
public class RedisConfig {
private RedisConnectionFactory redisConnectionFactory = null;
@Bean
public RedisConnectionFactory initRedisConnectionFactory() {
if (this.redisConnectionFactory != null) {
return redisConnectionFactory;
}
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
redisStandaloneConfiguration.setHostName("192.168.3.253");
redisStandaloneConfiguration.setPassword(RedisPassword.of("123456"));
redisStandaloneConfiguration.setPort(6379);
JedisPoolConfig poolConfig = new JedisPoolConfig();
//最大空闲数
poolConfig.setMaxIdle(30);
//最大连接数
poolConfig.setMaxTotal(50);
//最大等待毫秒
poolConfig.setMaxWaitMillis(2000);
JedisClientConfiguration jedisClientConfiguration = JedisClientConfiguration.builder().usePooling().poolConfig(poolConfig).build();
//创建Jedis连接工厂
JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(redisStandaloneConfiguration, jedisClientConfiguration);
//获取单机的Redis配置
this.redisConnectionFactory = jedisConnectionFactory;
return jedisConnectionFactory;
}
@Bean(name = "redisTemplate")
public RedisTemplate<Object, Object> initRedisTemplate() {
RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(initRedisConnectionFactory());
//使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值(默认使用JDK的序列化方式)
Jackson2JsonRedisSerializer jacksonSeial = new Jackson2JsonRedisSerializer(Object.class);
ObjectMapper om = new ObjectMapper();
// 指定要序列化的域,field,get和set,以及修饰符范围,ANY是都有包括private和public
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
// 指定序列化输入的类型,类必须是非final修饰的,final修饰的类,比如String,Integer等会跑出异常
om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
jacksonSeial.setObjectMapper(om);
// 值采用json序列化
redisTemplate.setValueSerializer(jacksonSeial);
//使用StringRedisSerializer来序列化和反序列化redis的key值
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 设置hash key 和value序列化模式
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(jacksonSeial);
redisTemplate.afterPropertiesSet();
return redisTemplate;
}
}
# 配置连接池属性
spring.redis.jedis.pool.max-active=10
spring.redis.jedis.pool.max-idle=10
spring.redis.jedis.pool.max-wait=2000
spring.redis.jedis.pool.min-idle=5
#配置redis服务器属性
spring.redis.port=6379
spring.redis.host=192.168.3.253
spring.redis.password=123456
#redis连接超时时间
spring.redis.timeout=1000
这样springboot就会自动注入RedisTemplate和StringRedisTemplate到Ioc容器,但是我们还需要配置一下redis的序列化方式,方便我们追踪查看。
package com.lay.redis.config;
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration
public class BootRedisConfig {
@Autowired
private RedisTemplate redisTemplate = null;
//定义自定义后初始化方式
@PostConstruct
public void init() {
initRedisTemplate();
}
private void initRedisTemplate() {
RedisSerializer stringSerializer = redisTemplate.getStringSerializer();
redisTemplate.setKeySerializer(stringSerializer);
redisTemplate.setValueSerializer(stringSerializer);
redisTemplate.setHashKeySerializer(stringSerializer);
redisTemplate.setHashValueSerializer(stringSerializer);
}
}
使用注解@PostConstruct
自定义后初始化方法,在配置文件Bean加载时调用。这样就完成了redis的自定义序列化方式。
我们先看下spring-data-redis 数据类型封装操作接口
操作接口 | 功能 |
---|---|
HashOperations | 散列操作接口 |
ListOperations | 列表(链表)操作接口 |
SetOperations | 集合(散列表)操作接口 |
ValueOperations | 字符串操作接口 |
ZSetOperations | 有序集合操作接口 |
GeoOpeations | 地理位置操作接口(使用不多) |
HyperLogLogOperations | 基数操作接口(使用不多) |
它们都可以通过RedisTemplate或者StringRedisTemplate得到。
redisTemplate.opsForHash();
redisTemplate.opsForList();
redisTemplate.opsForSet();
redisTemplate.opsForValue();
redisTemplate.opsForZSet();
...
需要对某一个键值对(key-value)做连续操作的时候。
接口 | 说明 |
---|---|
BoundHashOperations | 绑定一个散列数据类型的键进行操作 |
BoundListOperations | 绑定一个列表数据类型的键进行操作 |
BoundSetOperations | 绑定一个集合数据类型的键进行操作 |
BoundValueOperations | 绑定一个字符串数据类型的键进行操作 |
BoundZSetOperations | 绑定一个有序集合数据类型的键进行操作 |
方式
BoundHashOperations hashOps=redisTemplate.boundHashOps("key");
//其余类似
@Controller
@RequestMapping(value = "/redis")
public class RedisController {
@Autowired
private RedisTemplate redisTemplate = null;
@Autowired
private StringRedisTemplate stringRedisTemplate = null;
/**
* 操作redis字符串和hash散列
* @return
* @Date 2018年11月1日 下午1:56:30
* @Author lay
*/
@RequestMapping(value = "/stringAndHash")
@ResponseBody
public Map<String, Object> testStringAndHash() {
//set字符串
redisTemplate.opsForValue().set("key1", "value1");
System.out.println("-------------set字符串-------------------: " + redisTemplate.opsForValue().get("key1"));
//注意这里使用了JDK的序列化器,所以redis保存时不是整数,不能运算
redisTemplate.opsForValue().set("int_key", "1");
System.out.println("-------------set int_key-------------------: " + redisTemplate.opsForValue().get("int_key"));
stringRedisTemplate.opsForValue().set("int", "1");
System.out.println("-------------stringRedisTemplate set int-------------------: " + redisTemplate.opsForValue().get("int"));
//使用运算
stringRedisTemplate.opsForValue().increment("int", 1);
System.out.println("-------------使用运算+1-------------------: " + redisTemplate.opsForValue().get("int"));
//获得底层jedis连接
Jedis jedis = (Jedis)stringRedisTemplate.getConnectionFactory().getConnection().getNativeConnection();
//减一操作,这个命令RedisTemplate不支持,所以先获取上面的底层连接再操作。
jedis.decr("int");
System.out.println("-------------使用底层jedis -1-------------------: " + redisTemplate.opsForValue().get("int"));
//定义一个hashmap散列
Map<String, String> hash = new HashMap<String, String>();
hash.put("field1", "value1");
hash.put("field2", "value2");
//存入一个散列数据类型
stringRedisTemplate.opsForHash().putAll("hash", hash);
System.out.println("-------------存入一个散列数据类型-------------------: ");
System.out.println("-------------map 遍历-------------------: ");
redisTemplate.opsForHash().entries("hash").forEach((k, v) -> {
System.out.println(k + ": " + v);
});
System.out.println("-------------map->set 遍历-------------------: ");
redisTemplate.opsForHash().keys("hash").forEach(key -> {
System.out.println(key + ": " + redisTemplate.opsForHash().get("hash", key));
});
//新增一个字段
stringRedisTemplate.opsForHash().put("hash", "field3", "value3");
System.out.println("-------------新增一个字段 field3-------------------: ");
redisTemplate.opsForHash().entries("hash").forEach((k, v) -> {
System.out.println(k + ": " + v);
});
//绑定散列操作的key,这样可以连续对同一个散列数据进行操作
BoundHashOperations hashOps = stringRedisTemplate.boundHashOps("hash");
//删除两个字段
hashOps.delete("field1", "field2");
System.out.println("-------------删除两个字段 field1 field2-------------------: ");
hashOps.entries().forEach((k, v) -> {
System.out.println(k + ": " + v);
});
//新增一个字段
hashOps.put("field5", "value5");
System.out.println("-------------新增一个字段 field5-------------------: ");
hashOps.entries().forEach((k, v) -> {
System.out.println(k + ": " + v);
});
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}
}
redis中列表是一种链表结构,意味着查询性能不高,而增删节点的性能高。
/**
* 测试redis list列表
* @return
* @Date 2018年11月1日 下午1:58:25
* @Author lay
*/
@RequestMapping(value = "list")
@ResponseBody
public Map<String, Object> testList() {
//插入两个链表,注意它们在链表中的顺序
//链表从左到右的顺序为v10,v8,v6,v4,v2
stringRedisTemplate.opsForList().leftPushAll("list1", "v2", "v4", "v6", "v8", "v10");
System.out.println("----------链表从左到右的顺序为v10,v8,v6,v4,v2----------------:");
stringRedisTemplate.opsForList().range("list1", 0, stringRedisTemplate.opsForList().size("list1") - 1).forEach(s -> System.out.println(s));
//从左到右顺序为v1,v2,v3,v4,v5,v6
stringRedisTemplate.opsForList().rightPushAll("list2", "v1", "v2", "v3", "v4", "v5", "v6");
System.out.println("----------从左到右顺序为v1,v2,v3,v4,v5,v6----------------:");
stringRedisTemplate.opsForList().range("list2", 0, stringRedisTemplate.opsForList().size("list2") - 1).forEach(s -> System.out.println(s));
// 绑定list2链表操作
BoundListOperations listOps = stringRedisTemplate.boundListOps("list2");
//从右边弹出一个成员
String result1 = (String)listOps.rightPop();
System.out.println("----------从右边弹出一个成员----------------: " + result1);
listOps.range(0, listOps.size() - 1).forEach(s -> System.out.println(s));
//获取定位元素,redis从0开始运算
String result2 = (String)listOps.index(1);
System.out.println("----------获取定位元素,redis从0开始运算----------------: " + result2);
listOps.range(0, listOps.size() - 1).forEach(s -> System.out.println(s));
//从左边插入链表
listOps.leftPush("v0");
System.out.println("----------从左边插入链表----------------: " + "v0");
listOps.range(0, listOps.size() - 1).forEach(s -> System.out.println(s));
//链表长度
Long size = listOps.size();
//求链表下标区间成员,整个链表下标范围为0到size-1,这里不取最后一个元素
List elements = listOps.range(0, size - 2);
System.out.println("----------求链表下标区间成员0->size-2 ----------------: " + "v0");
elements.forEach(s -> System.out.println(s));
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}
/**
* 测试redis set列表
* @return
* @Date 2018年11月1日 下午1:58:25
* @Author lay
*/
@RequestMapping(value = "set")
@ResponseBody
public Map<String, Object> testSet() {
//请注意,这里v1重复两次,因为集合不允许重复,所以只是插入5个成员到集合中
stringRedisTemplate.opsForSet().add("set1", "v1", "v1", "v2", "v3", "v4", "v5");
stringRedisTemplate.opsForSet().add("set2", "v2", "v4", "v6", "v8");
//绑定set1集合操作
BoundSetOperations setOps = stringRedisTemplate.boundSetOps("set1");
//增加两个元素
setOps.add("v6", "v7");
//删除两个元素
setOps.remove("v1", "v7");
//返回所有元素
Set set1 = setOps.members();
// 成员数
Long size = setOps.size();
//求交集
Set inner = setOps.intersect("set2");
//求交集并且用新集合inter保存
setOps.intersectAndStore("set2", "inner");
//求差集
Set diff = setOps.diff("set2");
//求差集,并且用新集合diff保存
setOps.diffAndStore("set2", "diff");
//求并集
Set union = setOps.union("set2");
//求并集并且用新集合union保存
setOps.unionAndStore("set2", "union");
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}
在一些网站中,经常会有排名,如最热门的商品或者最大的购买卖家,都是常常见到的场景,对于这类排名,刷新往往需要及时,也涉及较打的统计,如果使用数据库会太慢。
为了支持集合的排序,Redis提供了有序集合zset,有序集合和集合的差异并不大,它也是一种散列存储的方式,同时它的有序性只是靠它在数据结构中增加了一个属性——score(分数)得以支持。为了支持这个变化,spring提供了TypedTuple
接口,它定义了两个方法,并且Spring还提供了其默认的实现类DefaultTypedTuple。
它有两个属性,value和score(double)和getter/setter方法方便我们获取。
/**
* 测试redis zset
* @return
* @Date 2018年11月1日 下午1:58:25
* @Author lay
*/
@RequestMapping(value = "zset")
@ResponseBody
public Map<String, Object> testZset() {
Set<TypedTuple<String>> typedTupleSet = new HashSet<>();
for (int i = 1; i <= 9; i++) {
//分数
double score = i * 0.1;
//创建一个TypedTuple对象,存入值和分数
TypedTuple<String> typedTuple = new DefaultTypedTuple<String>("value" + i, score);
typedTupleSet.add(typedTuple);
}
//往有序集合插入元素
stringRedisTemplate.opsForZSet().add("zset1", typedTupleSet);
//绑定zset1有序集合操作
BoundZSetOperations zsetOps = stringRedisTemplate.boundZSetOps("zset1");
System.out.println("------------init----------------");
zsetOps.rangeWithScores(0, zsetOps.size() - 1).forEach(new Consumer() {
@Override
public void accept(Object t) {
TypedTuple<String> s = (TypedTuple)t;
System.out.println(s.getValue() + " : " + s.getScore());
}
});
//增加一个元素
zsetOps.add("value10", 0.26);
System.out.println("------------增加一个元素 value10----------------");
zsetOps.rangeWithScores(0, zsetOps.size() - 1).forEach(new Consumer() {
@Override
public void accept(Object t) {
TypedTuple<String> s = (TypedTuple)t;
System.out.println(s.getValue() + " : " + s.getScore());
}
});
// 获得range 1---6
Set<String> setRange = zsetOps.range(1, 6);
System.out.println("------------获得range 1---6---------------");
Iterator itor = setRange.iterator();
if (itor.hasNext()) {
String s = (String)itor.next();
System.out.println(s);
}
//按分数排序获得有序集合
Set<String> setScore = zsetOps.rangeByScore(0.2, 0.6);
System.out.println("------------按分数排序获得有序集合 (0.2, 0.6)---------------");
Iterator itor2 = setScore.iterator();
if (itor.hasNext()) {
String s = (String)itor.next();
System.out.println(s);
}
//自定义范围
Range range = new Range();
range.gt("value3");//大于value3
// range.gte("value3");//大于等于value3
// range.lt("value8");//小于value8
range.lte("value8");//小于等于value8
//按值排序,请注意这个排序是按字符串排序
Set<String> setLex = zsetOps.rangeByLex(range);
System.out.println("------------自定义范围 (value3, value8)---------------");
Iterator itor3 = setLex.iterator();
if (itor.hasNext()) {
String s = (String)itor.next();
System.out.println(s);
}
//删除元素
zsetOps.remove("value9", "value2");
System.out.println("------------删除元素 value9, value2---------------");
zsetOps.rangeWithScores(0, zsetOps.size() - 1).forEach(new Consumer() {
@Override
public void accept(Object t) {
TypedTuple<String> s = (TypedTuple)t;
System.out.println(s.getValue() + " : " + s.getScore());
}
});
//求分数
Double score = zsetOps.score("value8");
System.out.println("-----------求分数 value8---------------:" + score);
//在下标区间下,按分数排序,同时返回value和score
Set<TypedTuple<String>> rangeSet = zsetOps.rangeWithScores(1, 6);
System.out.println("-----------在下标区间下,按分数排序 (1, 6)---------------");
rangeSet.forEach(new Consumer() {
@Override
public void accept(Object t) {
TypedTuple<String> s = (TypedTuple)t;
System.out.println(s.getValue() + " : " + s.getScore());
}
});
//在分数区间下,按分数排序,同时返回value和score
Set<TypedTuple<String>> scoreSet = zsetOps.rangeByScoreWithScores(0.1, 0.6);
System.out.println("-----------在分数区间下,按分数排序 (0.1, 0.6)---------------");
scoreSet.forEach(new Consumer() {
@Override
public void accept(Object t) {
TypedTuple<String> s = (TypedTuple)t;
System.out.println(s.getValue() + " : " + s.getScore());
}
});
//按从大到小排序
Set<String> reverseSet = zsetOps.reverseRange(0, zsetOps.size() - 1);
System.out.println("-----------按从大到小排序---------------");
reverseSet.forEach(s -> System.out.println(s));
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}
/**
* redis 开启事务
* @return
* @Date 2018年11月2日 上午10:13:19
* @Author lay
*/
@RequestMapping(value = "/multi")
@ResponseBody
public Map<String, Object> testMulti() {
redisTemplate.opsForValue().set("key1", "value1");
List list = (List)redisTemplate.execute((RedisOperations operations) -> {
//设置要监控的Key
operations.watch("key1");
//开启事务。在exec命令执行前,全部都只是进入队列
operations.multi();
operations.opsForValue().set("key2", "value2");
//获取值为null,因为redis只是把命令放入队列
Object value2 = operations.opsForValue().get("key2");
System.out.println("命令在队列中,所以key2为null【" + value2 + "】");
operations.opsForValue().set("key3", "value3");
System.out.println("命令在队列中,所以key3为null【" + value2 + "】");
//执行exec命令,将先判断key1是否在监控后被修改过,如果是则不执行事务,否则就执行事务
return operations.exec();
});
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}
/**
* redis 流水线
* @return
* @Date 2018年11月2日 上午10:13:29
* @Author lay
*/
@RequestMapping(value = "/pipeline")
@ResponseBody
public Map<String, Object> testPipeLine() {
Long start = System.currentTimeMillis();
List list = redisTemplate.executePipelined((RedisOperations operations) -> {
for (int i = 1; i <= 100000; i++) {
operations.opsForValue().set("pipeline_" + i, "value" + i);
String value = (String)operations.opsForValue().get("pipeline_" + i);
if (i == 100000) {
System.out.println("命令在队列中,所以值为null【" + value + "】");
}
}
return null;
});
Long end = System.currentTimeMillis();
System.out.println("耗时: " + (end - start) + "毫秒");
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}
@RequestMapping(value = "/publish")
@ResponseBody
public Map<String, Object> testPublish() {
List list = new ArrayList<>();
list.add("java");
list.add("python");
list.add("c++");
redisTemplate.convertAndSend("topic1", list);
Map<String, Object> map = new HashMap<String, Object>();
map.put("success", true);
return map;
}