Java和SpringBoot操作redis
2. java操作Redis
2.1 环境准备
- 引入依赖
<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 # 查看防火墙的状态sytemctl disable 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"); // zrangeByScorejedis.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类型,当然我们在使用的时候是不用写泛型的StringRedisTemplateRedisTemplate在操作的时候key和value都是Object类型,当然我们在使用的时候是不用写泛型的RedisTemplate
注意: 使用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应用的端口为8989server.port=8989# 配置操作redis相关的(redis服务器的主机ip和端口和使用哪个库)spring.redis.host=192.168.72.129spring.redis.port=7000spring.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)