Redis 的常用命令在其他的文章中都已经介绍完了。作为程序员不是要在命令行中使用 Redis,毕竟我们要把 Redis 当做缓存、队列等进行使用时,因此重点还是要在代码中使用。那么,我们就需要去掌握 Redis 相关的 API 的使用方法。
Spring Boot 整合 Redis
目前主流的 Java 项目都在使用 Spring Boot,那么我们就来在 Spring Boot 中整合 Redis。
Spring Boot 使用“约定大于配置”的方式逐步的取代了早起通过 XML 进行配置的方式,使得在 Spring Boot 中整合各种库或者依赖都非常的方便。在我们创建 Spring Boot 项目时,选择相关的 Starter 时,在 “NoSQL” 中把 “Spring Data Redis” 项勾选即可。这样就会在 pom 文件中添加相关的依赖,依赖如下:
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-data-redisartifactId>dependency>
有了 Redis 的依赖之后,我们就可以在项目中很容易的使用 Redis 的 API 来对 Redis 进行操作了。
Redis API 介绍
Spring Boot 提供的 Redis API 分为 高阶 API 和 低阶 API,高阶 API 是经过一定封装后的 API,而低阶 API 的使用和直接使用 Redis 的命令差不多。
高阶 API 提供了两个类可以供我们使用,分别是 RedisTemplate 和 StringRedisTemplate。StringRedisTemplate 继承自 RedisTemplate,StringRedisTemplate 的序列化方式与 RedisTemplate 的序列化的方式不同。具体在使用上的差别不是特别明显,但是数据在存储到 Redis 中的时候,因为序列化方式的不同,会有一定的差别。
低阶 API 其实也是通过 RedisTemplate 或 StringRedisTemplate 来进行获取。低阶 API 的方法和 Redis 的命令差不多。
Redis 高阶 API 使用
先来看看 高阶 API 的使用方法,具体就是 RedisTemplate 和 StringRedisTemplate 两个类的使用。这里不解释具体过多的原理,主要来看看它们 API 的使用,与存储的不同。
我们来创建一个 TestRedis 的类,并通过注解将其声明为一个 Component,并注入 RedisTemplate 和 StringRedisTemplate 两个类,代码如下。
@Componentpublic class TestRedis{ @Autowired RedisTemplate redisTemplate; @Autowired StringRedisTemplate stringRedisTemplate; public void redis() { }}
我们对 Redis 的所有操作就在这个类中完成。由于我们对项目中没有引入 SpringMVC 等,因此,测试 TestRedis 就直接通过 Spring Boot 的启动类来直接进行调用,启动类的代码如下:
public static void main(String[] args){ ConfigurableApplicationContext run = SpringApplication.run(SpringbootRedisApplication.class, args); TestRedis bean = run.getBean(TestRedis.class); bean.redis();}
我们接着来测试一下 RedisTemplate 这个类,用它来进行值为字符串的操作,代码如下:
public void testRedisTemplate(){ ValueOperations valueOperations = redisTemplate.opsForValue(); valueOperations.set("key1", "value1"); valueOperations.set("key2", "哈哈"); System.out.println(valueOperations.get("key1")); System.out.println(valueOperations.get("key2"));}
我们通过 redisTemplate 的 opsForValue 得到一个 ValueOperations 的实例,然后通过它来操作 Redis 的字符串类型。使用它的 set 方法添加了 key1 和 key2 两个键值对。然后,通过它的 get 方法又读取了这两个键对应的值。通过控制台的输出也可以看出,操作是成功的,控制台输出如下:
testRedisTemplate...value1哈哈
虽然在代码中操作是没问题的,但是,我们通过 Redis 的客户端来进行查看一下。
127.0.0.1:6379> keys *1) "\xac\xed\x00\x05t\x00\x04key2"2) "\xac\xed\x00\x05t\x00\x04key1"127.0.0.1:6379> get \xac\xed\x00\x05t\x00\x04key2(nil)
从 Redis 的客户端查看,看到有一些 16 进制的字符,而且也不方便我们进行 get 操作。我们换一种启动 Redis 客户端的方式,在启动 Redis 客户端时,我们来加一个 --raw 的参数,再来查看我们写入的键值对,如下:
[root@node01 ~]# redis-cli --raw127.0.0.1:6379> keys *��tkey2��tkey1
可以看到,在键的前面仍然有一些“奇怪”的字符,这些奇怪的字符就是前面看到的那些 16 进制字符。我们来换 StringRedisTemplate 进行相同的操作,代码如下:
public void testStringRedisTemplate(){ ValueOperations stringStringValueOperations = stringRedisTemplate.opsForValue(); stringStringValueOperations.set("key3", "value3"); stringStringValueOperations.set("key4", "哈哈"); System.out.println(stringStringValueOperations.get("key3")); System.out.println(stringStringValueOperations.get("key4"));}
我们把前面代码注释掉,直接执行我们现在的代码,执行结果如下。
testStringRedisTemplate...value3哈哈
我们在到 Redis 中看一下,如下:
127.0.0.1:6379> keys *key4key3127.0.0.1:6379> get key4哈哈127.0.0.1:6379> get key3value3
使用 RedisTemplate 和 StringRedisTemplate 之所以有所差异,是因为 StringRedisTemplate 在对 Key 和 Value 进行序列化时,都使用字符串的方式进行序列化。因此,我们选择 StringRedisTemplate 会方便一些。
Redis 低阶 API 使用
Redis 的 低阶 API 使用是相对比较麻烦的。同样使用一个例子来进行查看。
public void testLowApi(){ RedisConnection connection = stringRedisTemplate.getConnectionFactory().getConnection(); connection.set("key5".getBytes(), "value5".getBytes()); connection.set("key6".getBytes(), "哈哈".getBytes()); byte[] bytes = connection.get("key5".getBytes()); System.out.println(new String(bytes)); byte[] bytes1 = connection.get("key6".getBytes()); System.out.println(new String(bytes1));}
运行代码,输出如下:
testLowApi...value5哈哈
同样的,我们到 Redis 的客户端中进行查看,如下:
127.0.0.1:6379> keys *key4key3key6key5127.0.0.1:6379> get key5value5127.0.0.1:6379> get key6哈哈
看着效果也是不错的。不过它的操作过于繁琐,我们还是不考虑使用它。虽然不考虑使用它,但是简单的了解还是要有的。
Redis 高阶 API 操作 Hash
在 Redis 中,使用哈希结构还是比较常见的。这里给出一个简单的操作哈希结构的代码,我们进行一些简单的操作,代码如下:
public void optHash(){ HashOperations<String, Object, Object> stringObjectObjectHashOperations = stringRedisTemplate.opsForHash(); stringObjectObjectHashOperations.put("no1", "name", "zhangsan"); stringObjectObjectHashOperations.put("no1", "age", 22); System.out.println(stringObjectObjectHashOperations.entries("no1"));}
我们来运行一下代码,运行结果如下:
optHash...Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:36) at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:185) at org.springframework.data.redis.core.DefaultHashOperations.put(DefaultHashOperations.java:189) at cn.coderup.TestRedis.optHash(TestRedis.java:71) at cn.coderup.TestRedis.redis(TestRedis.java:32) at cn.coderup.SpringbootRedisApplication.main(SpringbootRedisApplication.java:16)
可以看到,运行代码时报错了,原因是在进行转换时发生了异常。通过异常抛出的信息可以看出,在 StringRedisSerializer 进行序列化时发生的异常,这个问题是,在我们代码中的第二个 put 方法时传入了一个整型的 22 进去,这里将它改为字符串的 22,修改后的代码如下:
stringObjectObjectHashOperations.put("no1", "age", "22");
运行修改后的代码,查看输出结果:
optHash...{name=zhangsan, age=22}
Redis 高阶 API 操作类
上面的代码虽然完成了对哈希的操作,但是略显繁琐,类似上面的操作我们实际中并不会去逐个的进行 put 操作,更多的应该是将一个实体类存储到 Redis 的哈希类型中。我们通过代码演示一下。
先定义一个学生类,定义如下:
public class Student{ private String name; private Integer age;
我省去了代码中的 getter 和 setter 方法,大家补全即可。除此而外,需要引入另外一个依赖,用于将对象转为 HashMap 或 HashMap 转换为对象。pom文件中添加的依赖如下:
<dependency> <groupId>org.springframework.bootgroupId> <artifactId>spring-boot-starter-jsonartifactId>dependency>
有了上面的依赖以后,我们来接着完成代码,首先将 ObjectMapper 进行注入:
@AutowiredObjectMapper objectMapper;
注入 ObjectMapper 后,我们来将实体类写入 Redis 的哈希类型中,代码如下:
public void optHashForObj(){ Student student = new Student(); student.setName("zhangsan"); student.setAge(20); Jackson2HashMapper jackson2HashMapper = new Jackson2HashMapper(objectMapper, false); HashOperations<String, Object, Object> stringObjectObjectHashOperations = stringRedisTemplate.opsForHash(); // 对象转Mapper stringObjectObjectHashOperations.putAll("no2", jackson2HashMapper.toHash(student)); Map<Object, Object> no2 = stringObjectObjectHashOperations.entries("no2"); // Map转对象 Student student1 = objectMapper.convertValue(no2, Student.class); System.out.println(student1.getName()); System.out.println(student1.getAge());}
这样我们的代码看似就完成了,那么我们执行一下,看会输出什么内容,输出如下:
optHashForObj...Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String at org.springframework.data.redis.serializer.StringRedisSerializer.serialize(StringRedisSerializer.java:36) at org.springframework.data.redis.core.AbstractOperations.rawHashValue(AbstractOperations.java:185) at org.springframework.data.redis.core.DefaultHashOperations.putAll(DefaultHashOperations.java:147) at cn.coderup.TestRedis.optHashForObj(TestRedis.java:98) at cn.coderup.TestRedis.redis(TestRedis.java:44) at cn.coderup.SpringbootRedisApplication.main(SpringbootRedisApplication.java:16)
可以看出,又抛出了异常,问题同样是因为我们的 Student 类中的 age 属性是 Integer 类型。那么,是不是需要我们将 Integer 类型换成 String 类型呢?如果真的需要将 Integer 类型全部修改为 String 类型的话,那么在实际的项目中就是一场灾难了。那么具体怎么办呢?其实只要修改 Hash 的序列化器,我们在 stringRedisTemplate.opsForHash() 这句代码上增加一句代码,代码如下:
stringRedisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class));
再次运行我们的代码,执行结果如下:
optHashForObj...zhangsan20
通过 Redis 的客户端来查看我们写入的 Hash 类型的值,也是没有任何问题的。
全局的 Redis 配置
在上面的代码中,可以通过设置 Hash 类型的序列化器从而方便的使用 putAll() 方法将一个类写入 Redis 中。但是,每次这样的写入都会特别的繁琐,我们定义一个全局的 Redis 的配置类,以后就无需每次使用都进行序列化器的设置了。
我们添加一个 RedisConfig 类,代码如下:
@Configurationpublic class RedisConfig{ @Autowired RedisConnectionFactory redisConnectionFactory; @Bean public StringRedisTemplate getStringRedisTemplate() { StringRedisTemplate stringRedisTemplate = new StringRedisTemplate(); stringRedisTemplate.setConnectionFactory(redisConnectionFactory); stringRedisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer<Object>(Object.class)); return stringRedisTemplate; }}
有了上面的代码,我们就无需每次都设置序列化器了。为了进行测试,我们删掉前面代码中设置序列化器的代码,然后再执行代码看是否报错,结果是没有问题,不会报错。
总结
关于在 Spring Boot 中简单整合 Redis 的方法就基本完成了。当然了,这里没有介绍具体如何操作各种数据结构的方法,以及一些 Redis 的实际使用场景,后续再进行介绍吧。