2. java操作Redis
2.1 环境准备
- 引入依赖
<!-- https://mvnrepository.com/artifact/redis.clients/jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
jedis这个名字是java和redis名字的结合,意为java操作redis
- 创建 Jedis 对象
public static void main(String[] args) {
//1. 创建jedis对象
//1.redis服务必须关闭防火墙 2.redis服务必须开启远程连接
Jedis jedis = new Jedis("192.168.72.129", 7000);
//选择操作的库
// 如果不传参数的话表示默认连接的是本机的Redis,端口号为6379
jedis.select(0);
//2. 执行相关操作
//....
//3. 释放资源
jedis.close();
}
- 其他配置
如果想要操作使用java操作redis,我们还需要做如下配置:
- redis服务器是否开放了指定端口
- 修改配置文件(关闭保护模式,注释掉 bind)
- 关闭防火墙
查看redis服务器是否开放了这个端口,这里以 7000 端口为例
- 查看是否打开端口号“7000”
firewall-cmd --query-port=7000/tcp
- 如果返回值为“no”,则输入此命令,开启该“7000”端口
firewall-cmd --add-port=7000/tcp
修改配置文件:关闭保护模式,注释掉 bind 127.0.0.1
查看防火墙状态,如果防火墙开启要关闭防火墙
Centos7默认安装了firewalld,如果没有安装的话,可以使用 yum install firewalld firewalld-config进行安装。
yum install firewalld firewalld-config # 默认安装了防火墙,如果没有安装,使用这个命令安装
firewall-cmd --state # 查看防火墙的状态
systemctl stop firewalld # 关闭防火墙
systemctl start firewalld.service # 开启防火墙
之后重启redis就可以了
2.2 相关操作
java操作redis的api很多都是和redis的指令一一对应的,很方便,所以下面的api没有写的太详细,很多api基本都是和redis指令是一样的格式
1. 操作库相关的
// 操作库相关的
public class TestRedis {
public static void main(String[] args) {
// 创建Jedis对象连接Redis,传入两个参数,分别是Redis服务器的主机地址以及端口号
// 如果不传参数的话表示默认连接的是本机的Redis,端口号为6379
Jedis jedis = new Jedis("192.168.72.129", 7000);
// 选择使用的库,如果不写的话默认使用的是0号库
jedis.select(0);
// 获取redis中当前库所有key信息
Set<String> keys = jedis.keys("*");
keys.forEach(key -> System.out.println("key = " + key));
// 操作库相关的
jedis.flushDB(); // 清空当前库
jedis.flushAll(); // 清空所有库
// 释放资源
jedis.close();
}
}
2. 操作key相关的
// 一下api没有写,其实java操作redis中的api和redis都是一一对应的,很好写的
public class TestKey {
private Jedis jedis;
@Before
public void before(){
jedis = new Jedis("192.168.72.129", 7000);
jedis.select(0);
}
// 测试key相关的
@Test
public void testKeys(){
// 删除一个key
jedis.del("name");
// 删除多个key
jedis.del("name", "age");
// 判断一个key是否存在
Boolean age = jedis.exists("age");
System.out.println(age);
// 设置一个key的超时时间
Long expire = jedis.expire("name", 100);
System.out.println(expire);
// 获取一个key的超时时间
Long ttl = jedis.ttl("name");
System.out.println(ttl);
// 随机获取一个key
String s = jedis.randomKey();
System.out.println(s);
// 修改一个key的名字
jedis.rename("name", "newName");
// 查看key对应的value的类型
String s1 = jedis.type("maps");
System.out.println(s1);
}
@After
public void after(){
jedis.close();
}
}
3. 操作String相关的
// java操作redis的api很多都是和redis的指令一一对应的,很方便,所以这里不过多赘述
public class TestString {
private Jedis jedis;
@Before
public void before(){
jedis = new Jedis("192.168.72.129", 7000);
jedis.select(0);
}
// 测试String相关的
@Test
public void testString(){
// set
jedis.set("name", "小陈");
// get
String s = jedis.get("name");
System.out.println(s);
// mset
jedis.mset("content", "好人", "address", "海淀区");
// mget
List<String> mget = jedis.mget("content", "address");
mget.forEach(v -> System.out.println("v = " + v));
// getset
String set = jedis.getSet("name", "小明");
System.out.println(set);
// ....
}
@After
public void after(){
jedis.close();
}
}
4. 操作List相关的
// java操作redis的api很多都是和redis的指令一一对应的,很方便,所以这里不过多赘述
public class TestList {
private Jedis jedis;
@Before
public void before(){
jedis = new Jedis("192.168.72.129", 7000);
jedis.select(0);
}
// 测试List相关的
@Test
public void testList(){
// lpush
jedis.lpush("names", "张三", "李四", "王五", "赵六", "win7");
// rpush
jedis.rpush("names", "xiaomingming");
// lrange
List<String> names = jedis.lrange("names", 0, -1);
names.forEach(name -> System.out.println("name = " + name));
// lpop rpop
String names1 = jedis.lpop("names");
System.out.println(names1);
// llen
jedis.llen("names");
// linsert 在一个集合中某个元素的之前或之后插入元素
// BinaryClient.LIST_POSITION.BEFORE 之前插入元素
// BinaryClient.LIST_POSITION.AFTER 之后插入元素
jedis.linsert("names", BinaryClient.LIST_POSITION.AFTER, "xiaohei", "xiaobai");
// ...
}
@After
public void after(){
jedis.close();
}
}
5. 测试Set相关的
// java操作redis的api很多都是和redis的指令一一对应的,很方便,所以这里不过多赘述
public class TestSet {
private Jedis jedis;
@Before
public void before(){
jedis = new Jedis("192.168.72.129", 7000);
jedis.select(0);
}
// 测试Set相关的
@Test
public void testSet(){
// sadd
jedis.sadd("sets", "张三", "李四", "王五", "赵六");
// smembers
jedis.smembers("sets");
// sismember
jedis.sismember("names", "张三");
// ...
}
@After
public void after(){
jedis.close();
}
}
6. 测试ZSet相关的
// java操作redis的api很多都是和redis的指令一一对应的,很方便,所以这里不过多赘述
public class TestZSet {
private Jedis jedis;
@Before
public void before(){
jedis = new Jedis("192.168.72.129", 7000);
jedis.select(0);
}
// 测试ZSet相关的
@Test
public void testZSet(){
// zadd
jedis.zadd("zsets", 10, "张三");
// zrange
jedis.zrange("zsets", 0, -1);
// zcard
jedis.zcard("zsets");
// zrangeByScore
jedis.zrangeByScore("name", "0", "100", 0, 5);
// ...
}
@After
public void after(){
jedis.close();
}
}
7. 测试Hash相关的
// java操作redis的api很多都是和redis的指令一一对应的,很方便,所以这里不过多赘述
public class TestHash {
private Jedis jedis;
@Before
public void before(){
jedis = new Jedis("192.168.72.129", 7000);
jedis.select(0);
}
// 测试Hash相关的
@Test
public void testHash(){
// hget
jedis.hget("maps", "name");
// hgetall
jedis.hgetAll("maps");
// hkeys
jedis.hkeys("maps");
// hvals
jedis.hvals("maps");
//...
}
@After
public void after(){
jedis.close();
}
}
3. SpringBoot整合Redis
Spring Data(数据) 为Redis 提供了RedisTemplate和StringRedisTemplate,其中StringRedisTemplate是RedisTemplate的子类,两个方法基本一致,不同之处主要体现在操作的数据类型不同,**RedisTemplate中的两个泛型都是Object,意味着存储的key和value都可以是一个对象,而StringRedisTemplate的两个泛型都是String,意味着StringRedisTemplate的key和value都只能是字符串。**接下来我们来看看在SpringBoot中如何使用StringRedisTemplate和RedisTemplate这两个对象吧。
StringRedisTemplate在操作的时候key和value都是String类型,当然我们在使用的时候是不用写泛型的
StringRedisTemplate<String,String>
RedisTemplate在操作的时候key和value都是Object类型,当然我们在使用的时候是不用写泛型的
RedisTemplate<Object,Object>
注意: 使用RedisTemplate默认是将对象序列化到Redis中,所以放入的对象必须实现对象序列化接口
3.1 环境准备
使用快速构建方式构建一个SpringBoot模块
注:使用SpringBoot版本为2.2.5.RELEASE,模块构建完后之后记得修改一下
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 编写配置文件application.properties
# SpringBoot应用默认端口是8989,这里修改一下SpringBoot应用的端口为8989
server.port=8989
# 配置操作redis相关的(redis服务器的主机ip和端口和使用哪个库)
spring.redis.host=192.168.72.129
spring.redis.port=7000
spring.redis.database=0
在redis中还要开启远程连接
因为redis服务器上redis使用的是7000端口,所以我们还要检查 7000 端口是否开启,如果没有开启,我们要开启 7000端口
firewall-cmd --query-port=7000/tcp # 查看是否打开端口号“7000”,如果返回值为“no”,则输入下面命令
firewall-cmd --add-port=7000/tcp # 开启“7000”端口
3.2 使用StringRedisTemplate和RedisTemplate
因为很多都是和redis指令是对应的,所以下面测试的时候并没有写太多的api,在使用的时候根据提示望文生义使用或者查找即可,都是很好理解的。
使用 StringRedisTemplate 操作redis
StringRedisTemplate 在操作redis时传入的参数必须是String类型的,不能传入对象
import java.util.*;
import java.util.concurrent.TimeUnit;
// 下面两个注解相当于启动SpringBoot应用
@SpringBootTest(classes = RedisDay2Application.class) // 让SpringBoot启动起来,并指向入口类
@RunWith(SpringRunner.class)
public class TestStringRedisTemplate {
// 注入StringRedisTemplate
@Autowired
private StringRedisTemplate stringRedisTemplate; // key和value都是字符串
// StringRedisTemplate 只是传入的参数key和value都是字符串,并不代表它只能操作Redis中的String类型,它还可以操作List、Set等类型
// 操作redis中key相关的
@Test
public void testKey(){
//stringRedisTemplate.delete("name"); // 删除指定key
Boolean hasKey = stringRedisTemplate.hasKey("name");// 判断指定key是否存在
System.out.println(hasKey);
DataType dataType = stringRedisTemplate.type("name");// 判断key所对应value的类型
System.out.println(dataType);
Set<String> keys = stringRedisTemplate.keys("*"); // 获取redis中所有key
keys.forEach(key -> System.out.println("key = " + key));
Long expire = stringRedisTemplate.getExpire("name"); // 获取key的超时时间 -1 永不超时 -2 key不存在
System.out.println(expire);
String s = stringRedisTemplate.randomKey(); // 在redis中随机获取一个key
System.out.println(s);
//stringRedisTemplate.rename("age", "age1"); // 修改key的名字,要求key必须存在,不存在会报错
stringRedisTemplate.renameIfAbsent("name", "name1"); // 判断key是否存在,修改key的名字
stringRedisTemplate.move("name1", 1); // 移动key到指定库
}
// 操作redis中的字符串 opsForValue 实际就是操作redis中的String
@Test
public void testString(){
stringRedisTemplate.opsForValue().set("name", "小陈"); // 代表操作的类型是String类型,用来设置一个key-value对
String value = stringRedisTemplate.opsForValue().get("name"); // 代表操作的类型是String类型,用来获取一个key对应的value
System.out.println("value = " + value);
stringRedisTemplate.opsForValue().set("code", "2357", 120, TimeUnit.SECONDS); // 设置一个key的超时时间
stringRedisTemplate.opsForValue().append("name", "他是一个好人,单纯的少年");
//stringRedisTemplate.opsForList(); // 代表操作的类型是List类型,要操作List的话,在之后写.相应的方法即可
//stringRedisTemplate.opsForSet(); // 代表操作的是Set类型
//stringRedisTemplate.opsForZSet(); // 代表操作的是ZSet类型
//stringRedisTemplate.opsForHash(); // 代表操作的是Hash类型
}
// 操作redis中List类型 opsForList 实际就是操作redis中的List类型
@Test
public void testList(){
stringRedisTemplate.opsForList().leftPush("names", "小陈"); // 创建一个列表,并放入一个元素
stringRedisTemplate.opsForList().leftPushAll("names", "小张", "小王", "小明"); // 创建一个列表,放入多个元素
List<String> names = new ArrayList<>();
names.add("小明");
names.add("小王");
stringRedisTemplate.opsForList().leftPushAll("names", names); // 创建一个列表并放入一个String类型的集合
List<String> stringList = stringRedisTemplate.opsForList().range("names", 0, -1); // 遍历List
stringList.forEach(value -> System.out.println("value = " + value));
stringRedisTemplate.opsForList().trim("names", 0, 2); // 使这个key所对应的value只保留这个区间内的元素,其他元素截断
}
// 操作redis中Set类型 opsForSet 实际就是操作redis中的Set类型
@Test
public void testSet(){
stringRedisTemplate.opsForSet().add("sets", "小明", "小明", "小陈", "小王");
Set<String> sets = stringRedisTemplate.opsForSet().members("sets");
sets.forEach(value -> System.out.println("value = " + value));
Long size = stringRedisTemplate.opsForSet().size("sets"); // 获取set集合的元素个数
System.out.println("size = " + size);
}
// 操作redis中ZSet类型 opsForZSet 实际就是操作redis中的ZSet类型
@Test
public void testZSet(){
stringRedisTemplate.opsForZSet().add("zsets", "小陈", 100); // 创建一个ZSet并放入一个元素和相应的得分
stringRedisTemplate.opsForZSet().add("zsets", "小黑", 20);
Set<String> zsets = stringRedisTemplate.opsForZSet().range("zsets", 0, -1); // 遍历ZSet
zsets.forEach(value -> System.out.println("value = " + value));
// 获取指定Zset集合中指定范围的元素以及分数
Set<ZSetOperations.TypedTuple<String>> zsets1 = stringRedisTemplate.opsForZSet().rangeByScoreWithScores("zsets", 0, 1000);
zsets1.forEach(typedTuple -> {
System.out.println(typedTuple.getScore());
System.out.println(typedTuple.getValue());
});
}
// 操作redis中Hash类型 opsForHash 实际就是操作redis中的Hash类型
@Test
public void testHash(){
stringRedisTemplate.opsForHash().put("maps", "name", "张三"); // 创建一个Hash类型,并放入一个value为键值对类型的键值对
Map<String, String> map = new HashMap<>();
map.put("age", "18");
map.put("bit", "2012-12-12");
// 一次放入一个map集合,但要注意这个map集合的key和value都是String类型的
stringRedisTemplate.opsForHash().putAll("maps", map);
stringRedisTemplate.opsForHash().multiGet("maps", Arrays.asList("name", "age", "bir")); // 获取多个key,但注意要将多个key封装成Collection
String values = (String) stringRedisTemplate.opsForHash().get("maps", "name");// 获取value中某个key的值
Set<Object> maps = stringRedisTemplate.opsForHash().keys("maps");// 获取key对应的value中所有的键
List<Object> maps1 = stringRedisTemplate.opsForHash().values("maps");// 获取key对应的value中所有的值
}
}
使用 RedisTemplate 操作redis
RedisTemplate 在操作redis时传入的参数可以传入对象其实RedisTemplate 操作redis和StringRedisTemplate操作redis的api很像,只是传入的参数类型不同,RedisTemplate 可以传入对象,StringRedisTemplate只能传入字符串
在使用RedisTemplate 传入参数为对象的时候,必须将对象序列化,对象存储到redis之中的应该是对象序列化的结果,取出来的时候要展现也要进行反序列化。(对象没有办法存储到redis,但是对象序列化之后是可以存储的redis的,所以默认存储到redis中的key和value都是进行序列化的,如果是自定义的类,还要实现Serializable接口)
使用RedisTemplate 往redis里面存值的时候key和value都应该序列化之后再存进去,之后取value的时候应该根据key序列化之后从redis取,相同的key序列化的结果相同,存key的时候存的是key序列化的,如果取key的时候用的是key没有序列化去取,是取不到的。
对于String、Integet、Double、等等类型是默认进行了序列化的,对于自定义的类,我们要实现Serializable接口。
注意:由于在redis中的key都是String类型,所以 RedisTemplate 在实际操作的时候key都是String类型
# 关于 RedisTemplate 中的序列化机制
在RedisTemplate中默认 key 和 value默认都是 JdkSerializationRedisSerializer 序列化方案
但是我们说,这种方式存在局限性:我们在终端无法操作JdkSerializationRedisSerializerxuliehuadekey
在业务操作中,一般都是:
key 为 String
value 为 Object
所以我们要修改key的序列化方案为:StringRedisSerializer 即字符串的方式, value的序列化方案不用修改
# 修改key的序列化机制和Hash类型中value内部key的序列化机制为String序列化机制
// 修改key序列化方案 为String序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 修改Hash类型中value中key的序列化为String序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
用到的User类
// 下面这两个注解的作用是创建get、set方法,使用了下面的这两个注解就不需要写get、set方法了,会自动创建
@Data
@Accessors(chain = true)
public class User implements Serializable { // 在使用RedisTemplate操作redis的时候对象必须序列化
private String id;
private String name;
private Integer age;
private Date bir;
}
使用RedisTemplate
// 下面两个注解相当于启动SpringBoot应用
@SpringBootTest(classes = RedisDay2Application.class) // 让SpringBoot启动起来,并指向入口类
@RunWith(SpringRunner.class)
public class TestRedisTemplate {
@Autowired
private RedisTemplate redisTemplate;
// String、List、Set、ZSet、Hash
@Test
public void testRedisTemplate(){
/*
在RedisTemplate中默认 key 和 value默认都是 JdkSerializationRedisSerializer 序列化方案
但是我们说,这种方式存在局限性:我们在中断无法操作JdkSerializationRedisSerializerxuliehuadekey
在业务操作中,一般都是:
key 为 String
value 为 Object
所以我们要修改key的序列化方案为:StringRedisSerializer 即字符串的方式
value的序列化方案不用修改
*/
// 如果我们把key也序列化存进去,那我们在终端操作的时候就无法操作key了,因为redis
// 中存储的是key序列化的结果,我们在终端操作的时候有没有序列化、反序列化的操作,这种方式存储使得我们只可以
// 使用RedisTemplate操作,存在局限性
// 修改key序列化方案 为String序列化
redisTemplate.setKeySerializer(new StringRedisSerializer());
// 修改Hash类型中value中key的序列化为String序列化
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
User user = new User();
user.setId(UUID.randomUUID().toString()).setName("小陈").setAge(23).setBir(new Date());
// String默认实现了序列化反序列化而对于自定义的对象我们必须实现Serializable接口
redisTemplate.opsForValue().set("user", user);
// 在使用redisTemplate取的时候虽然我们直接传入了key,但是实际是先把key序列化之后再从redis中取的(默认)
// 在取值的时候虽然我们存value的时候也是序列化之后存的,会默认把value反序列化回来
System.out.println(redisTemplate.opsForValue().get("user"));
redisTemplate.opsForList().leftPush("list", user);
redisTemplate.opsForSet().add("set", user);
redisTemplate.opsForZSet().add("zset", user, 100);
redisTemplate.opsForHash().put("map", "name", user);
}
}
补充:关于 Bound API 的使用
当我们对于同一个key多次操作时,为了简便,提供了bound api,即对key进行绑定操作,StringRedisTemplate和RedisTemplate都可以使用Bound API
// 下面两个注解相当于启动SpringBoot应用
@SpringBootTest(classes = RedisDay2Application.class) // 让SpringBoot启动起来,并指向入口类
@RunWith(SpringRunner.class)
public class TestBoundAPI {
@Autowired
private RedisTemplate redisTemplate;
@Autowired
private StringRedisTemplate stringRedisTemplate;
// spring data 为了方便我们对redis进行更友好的操作 因此又提供了 bound api 简化操作
@Test
public void testBound(){
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
// 对同一个key的多次操作进行绑定, 对key绑定
stringRedisTemplate.opsForValue().set("name", "zhangsan");
stringRedisTemplate.opsForValue().set("name", "是一个好人");
String s = stringRedisTemplate.opsForValue().get("name");
System.out.println(s);
// 对字符串类型key进行绑定 后续所有操作都是基于这个key的操作
BoundValueOperations<String, String> nameValueOperations = stringRedisTemplate.boundValueOps("name");
nameValueOperations.set("zhangsan");
nameValueOperations.set("是一个好人");
String s1 = nameValueOperations.get();
System.out.println(s1);
// 对List的绑定操作
BoundListOperations<String, String> listOperations = stringRedisTemplate.boundListOps("lists");
listOperations.leftPushAll("张三", "李四", "小陈");
List<String> list = listOperations.range(0, -1);
list.forEach(value -> System.out.println("value = " + value));
// 对Set的绑定操作
BoundSetOperations<String, String> setOperations = stringRedisTemplate.boundSetOps("sets");
// 对ZSet的绑定操作
BoundZSetOperations<String, String> zsetOperations = stringRedisTemplate.boundZSetOps("zsets");
// 对Hash的绑定操作
BoundHashOperations<String, Object, Object> hashOperations = stringRedisTemplate.boundHashOps("map");
BoundValueOperations name = redisTemplate.boundValueOps("name");
BoundListOperations lists = redisTemplate.boundListOps("lists");
BoundSetOperations sets = redisTemplate.boundSetOps("sets");
BoundZSetOperations zsets = redisTemplate.boundZSetOps("zsets");
BoundHashOperations map = redisTemplate.boundHashOps("map");
}
}
总结
- 针对于日后处理key value 都是String 使用StringRedi sTemplate
- 针对于日后的key value 存在对象使用Redi sTemplate
- 针对于同一个key多次操作可以使用boundXXxOps ()的api简化书写(bound api)