什么是Redis
Redis是一种基于键值对(key-value)的NoSQL数据库
什么是Spring-redis
为了方便在spring工程中使用redis,创建的依赖包
配置Redis连接
为了操作Redis,我们需要使用Java的Redis客户端,下面是Redis官方推荐的三种客户端
IO方式 | 线程安全 | |
---|---|---|
Jedis | 阻塞式 | 否 |
Lettuce | 非阻塞 | 是 |
Redission | 非阻塞 | 是 |
Redis各个客户端的操作差别很大,为了屏蔽这种差异,spring提供了一套对应的抽象 spring-boot-starter-data-redis,它屏蔽了各种客户端的操作差异,使得我们可以使用相同的操作来使用Redis
<!-- 导入lettuce -->
<dependency>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</dependency>
<!-- 导入jedis -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
导入spring-boot-starter-data-redis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version></version>
</dependency>
spring-boor-starter-data-redis默认使用lettuce作为默认客户端,且仅支持jedis和lettuce
如果需要使用jedis作为操作Redis客户端,需要从spring-boor-starter-data-redis中排除lettuce依赖,并导入jedis
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<exclusions>
<exclusion>
<groupId>io.lettuce</groupId>
<artifactId>lettuce-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
spring-data-redis为redis提供的抽象
Redis的自动配置
spring为Redis设置了配置类xxxConnectionConfiguration类(xxx=Jedis/Lettuce);
其中有两个Bean xxxClientResources (提供构建客户端所需要的资源,和配置), redisConnectionFactory(用于建立和Redis的连接)
可以使用spring-actuator来查看这两个bean
配置连接信息
spring相关的配置前缀为spring.redis,具体的配置有RedisRroperties类实现:
# application.yml
spring:
redis:
host: 192.168.56.10
port: 6379
password: 123456
主要的配置信息如下
配置项 | 默认值 | 说明 |
---|---|---|
spring.redis.host | localhost | Redis 服务器主机名 |
spring.redis.port | 6379 | Redis 服务器端口 |
spring.redis.password | Redis 服务器密码 | |
spring.redis.timeout | 60s | 连接超时时间 |
spring.redis.sentinel.master | Redis 服务器主节点名称 | |
spring.redis.sentinel.nodes | 哨兵节点列表,节点用“主机名:端口”表示,主机之间用逗号分割 | |
spring.redis.sentinel.password | 哨兵节点密码 | |
springredis.cluster.nodes | 集群节点列表,节点可以自发现,但至少要配置一个节点 | |
spring.redis.cluster.maxRedirects | 5 | 在集群中执行命令时的最大重定向次数 |
spring.redis.jedis.pool.* | Jedis连接池配置 | |
spring.redis.lettuce.* | Lettuce特定的配置 |
Template类
spring将各种固有的操作封装成模板类如JdbcTemplate等,而redis中的常用操作被封装成了RedisTemplate类,
SpringBoot的RedisAutoConfiguration创建了两个默认的RedisTemplate对象,stringRedisTemplate类专用于字符串类型的Redis操作,redisTemplate用于通用类型的Redis操作。
Redis中有很多种数据结构,虽然键都是字符串类型的,但是由于值的种类各不相同,所以Template为了操作这些数据结构,必须要为每种数据结构提供一个操作集,对于每一种值都需要使用操作集包含的方法来执行操作
Template中封装的操作类型
private final ValueOperations<K, V> valueOps = new DefaultValueOperations(this);
private final ListOperations<K, V> listOps = new DefaultListOperations(this);
private final SetOperations<K, V> setOps = new DefaultSetOperations(this);
private final StreamOperations<K, ?, ?> streamOps = new DefaultStreamOperations(this, new ObjectHashMapper());
private final ZSetOperations<K, V> zSetOps = new DefaultZSetOperations(this);
private final GeoOperations<K, V> geoOps = new DefaultGeoOperations(this);
private final HyperLogLogOperations<K, V> hllOps = new DefaultHyperLogLogOperations(this);
private final ClusterOperations<K, V> clusterOps = new DefaultClusterOperations(this);
操作 | 描述 |
---|---|
ClusterOperations | Redis集群的相关操作 |
GeoOperations | Redis地理位置的相关操作 |
HashOperations | RedisHash类型的相关操作 |
HyperLogLogOperations | Redis HyperLogLog类型的相关操作 |
ListOperations | Redis列表类型的相关操作 |
SetOperations | Redis集合类型的相关操作 |
StreamOperations | Redis 流的相关操作 |
ValueOperations | Redis值类型的相关操作 |
ZSetOperations | Redis有序结合类型的相关操作 |
在使用Template时,我们调用Template对象的opsForXXX就可以获取到这些操作集,就可以使用操作集中的add(),get()等方法来操作Redis
于此同时,一些和Redis数据结构无关的操作直接封装到了Template类中,比如删除键,设置键的过期时间等操作;
Redis中存放对象
在Spring-redis中RedisTemplate负责将对象序列化并存储到redis中,Spring-data-redis默认会使用JDK自带的序列化机制来进行序列化和反序列化,而能够使用jdk序列化的类,需要实现Serializable
接口
public class Actor implements Serializable {
private static final long SERIALIZABLE_ID= 13543843449464L;
private Integer actorId;
private String firstName;
private String lastName;
public Actor(Integer actorId, String firstName, String lastName) {
this.actorId = actorId;
this.firstName = firstName;
this.lastName = lastName;
}
}
设置一个简单的示例来展示Redis存放java对象的
示例的目录
负责实现存放对象到Redis的逻辑的RedisServiceImpl
import org.example.entity.Actor;
import org.example.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@Service
public class RedisServiceImpl implements RedisService {
@Resource(name="redisTemplate")
RedisTemplate redisTemplate;
@Override
public void addActor(Actor actor) {
redisTemplate.opsForValue().set("Actor",actor);
}
@Override
public Actor getActor(String key) {
return (Actor)redisTemplate.opsForValue().get(key);
}
}
控制器类
import org.example.entity.Actor;
import org.example.service.RedisService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class ActorController {
@Autowired
private RedisService service;
@RequestMapping("test")
public String addActor(){
Actor actor=new Actor(115,"jonh","sin");
service.addActor(actor);
return "success";
}
@RequestMapping("get")
public Actor getActor(){
return service.getActor("Actor");
}
}
运行结果
运行完成之后我们可以看到Redis中增加了一个新的键值对,但是这种键值对无法直接使用,更不能实现在不同编程语言中共用这些缓存数据,我们希望使用JSON来实现对象的序列化和反序列化。
前面提到了RedisTemplate负责实现对象存储,这是由于RedisTemplate的初始化方法
中的下面这段决定的
if (this.defaultSerializer == null) {
this.defaultSerializer = new JdkSerializationRedisSerializer(this.classLoader!= null ? this.classLoader : this.getClass().getClassLoader());
}
if (this.enableDefaultSerializer) {
if (this.keySerializer == null) {
this.keySerializer = this.defaultSerializer;
defaultUsed = true;
}
if (this.valueSerializer == null) {
this.valueSerializer = this.defaultSerializer;
defaultUsed = true;
}
所以我们要更改Redis的序列化方式,只需要更改keySerializer和valueSerializer这两个序列化器即可,RedisSerializer类为我们提供了多种内置的序列化器,其中就有JSON的序列化器。
序列化器 | 快捷方式 | 说明 |
---|---|---|
JdkSerializationRedisSerializer | RedisSerializer.java() | 使用JDK的序列化方式 |
ByteArrayRedisSerializer | RedisSerializer.byteArray() | 直接透传 byte[],不做任何处理 |
StringRedisSerializer | RedisSerializer.string() | 根据字符集将字符串序列化为字节 |
GenericToStringSerializer | 依赖Spring的 ConversionService来序列字符串 | |
GenericJackson2JsonRedisSerializer | RedisSerializer.json() | 按照object来序列化对象 |
Jackson2JsonRedisSerializer | 根据给定的泛型类型序列化对象 | |
OxmSerializer | 依赖Spring的OXM(Object/XMLMapprO/M 映射器)来序列化对象 |
package org.example.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.core.RedisTemplate;
import org.springframework.data.redis.serializer.RedisSerializer;
@Configuration
public class RedisConfig {
@Bean(name="redisTemplate")
public RedisTemplate initRedisTemplate(RedisConnectionFactory fac){
RedisTemplate redisTemplate=new RedisTemplate();
redisTemplate.setConnectionFactory(fac);
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(RedisSerializer.json());
return redisTemplate;
}
}
所以我们添加一个配置类,其中覆盖了原生自动配置的redisTemplate,修改其keySerializer和ValueSerializer,并将需要被序列化为json的类修改为Java Bean的形式(提供构造函数和getter,setter方法) 然后重新启动程序,
public class Actor {
private static final long SERIALIZABLE_ID= 13543843449464L;
private Integer actorId;
private String firstName;
private String lastName;
public Actor() {
}
public Integer getActorId() {
return actorId;
}
public void setActorId(Integer actorId) {
this.actorId = actorId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
执行完成之后,可以看到redis直接将我们传入的key用字符串存储,然后对象使用JSON存储
在现实开发中,对象的属性也可能是一个对象,这个时候只需要将对象中的子对象也设置成JavaBean的格式,会自动存储为JSON格式,无需额外处理
package org.example.entity;
public class Actor {
private static final long SERIALIZABLE_ID= 13543843449464L;
private Integer actorId;
private String firstName;
private String lastName;
//添加了son属性用于测试
private Son son;
public Son getSon() {
return son;
}
public void setSon(Son son) {
this.son = son;
}
public Actor() {
}
public Integer getActorId() {
return actorId;
}
public void setActorId(Integer actorId) {
this.actorId = actorId;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
但是也不是所有的类都是我们自己定义的,能够被JSON序列化器直接序列化的,比如我们从依赖中导入的类。实际上,序列化器,Servializer的对象转换到Json是交给ObjectMapper类来处理的,我们可以通过定制ObjectMapper来覆盖SpringBoot自动配置的ObjectMapper,来控制类应该映射成什么样的json格式。下面以被广泛用于表示货币金额的Java库-Joda-Money来做解释,我们创建一个Java类:
import org.joda.money.Money;
public class Order {
Money money;
String orderName;
public Order() {
}
public Money getMoney() {
return money;
}
public void setMoney(Money money) {
this.money = money;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
}
导入org.joda.joda-money类库
<dependency>
<groupId>org.joda</groupId>
<artifactId>joda-money</artifactId>
<version>1.0.1</version>
<scope>runtime</scope>
</dependency>
不做处理直接使用,可以看到下面的类中许多信息是我们不需要的,我们只想将amount属性以特定的格式保存,所以这时候就需要自定义Order的ObjectMapper
-
定制Money类的序列化器和反序列化器
自定义序列化器(serializers)通常是通过Module方式注册到Jackson中,但在Spring Boot中提供了@JsonComponent注解作为替代方案,它能帮我们更为轻松的将序列化器注册到Spring Beans中。
我们可以直接在JsonSerializer 或 JsonDeserializer类上使用 @JsonComponent注解,该注解允许我们将带该注解的类公开为Jackson序列化器或反序列化器,而无需再手动将其添加到ObjectMapper。
-
有时jackson官方也会为我们提供一个默认的Moudule注册方案,我们只需要将这个Module注册到ObjectMapper中即可,一般来说只要注册为Bean,ObjectMapper会自动注入这个Module
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-joda-money -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-joda-money</artifactId>
<version>2.15.0</version>
</dependency>
Redis的Repository操作
为了简化Spring-redis的CRUD操作,我们可以使用Redis提供的Repository操作,其中包含了对这些简单操作的逻辑的封装,直接调用这些方法来即可。Repository操作可以这样理解:我们定义了一个仓库(仓库有一个仓库的名字),仓库中有很多实体类对象,这些对象分别有一个id来识别,这些对象可以使用我们提前准备好的工具来直接操作
- 定义实体类
实体类就是Repository操作的对象,
注解 | 说明 |
---|---|
@RedisHash | 与@Entity类似,用来定义Redis 的Repository操作的领域对象,其中的value定义了不同类型对象存储时使用的前缀,也叫做键空间 (keyspace),默认是全限定类名,timeToLive 用来定义缓存的秒数 |
@Id | 定义对象的标识符 |
@Indexed | 定义二级索引,加在属性上可以将该属性定义为查询用的索引 |
@Reference | 缓存对象引用,一般引用的对象也会被展开存储在当前对象中,添加了该注解后会直接存储该对象在Redis中的引用 |
@RedisHash(value="orderList",timeToLive = 60)
public class Order1 {
@Id
UUID uuid;
Money money;
String orderName;
public Order1() {
uuid=UUID.randomUUID();
}
public Money getMoney() {
return money;
}
public void setMoney(Money money) {
this.money = money;
}
public String getOrderName() {
return orderName;
}
public void setOrderName(String orderName) {
this.orderName = orderName;
}
}
2.定义接口
3. 定制某些特殊类的转换规则
有时候这些类自动被Repository操作时得到的结果是我们不满意的,如Joda Money类的自动转换,会多出很多不需要的属性
这时候除了像上一章一样为JodaMoney设置序列化器和反序列化器之外,还需要设置Converter来实现对象的转换。
BytesToMoney类
import com.fasterxml.jackson.databind.ObjectMapper;
import org.joda.money.Money;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
@ReadingConverter
public class BytesToMoney implements Converter<byte[],Money> {
private Jackson2JsonRedisSerializer<Money> serializer;
public BytesToMoney(ObjectMapper mapper) {
Jackson2JsonRedisSerializer serializer1=new Jackson2JsonRedisSerializer(Money.class);
this.serializer = serializer1;
this.serializer.setObjectMapper(mapper);
}
@Override
public Money convert(byte[] bytes) {
return serializer.deserialize(bytes);
}
}
MoneyToBytes类
import com.fasterxml.jackson.databind.ObjectMapper;
import org.joda.money.Money;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
@ReadingConverter
public class BytesToMoney implements Converter<byte[],Money> {
private Jackson2JsonRedisSerializer<Money> serializer;
public BytesToMoney(ObjectMapper mapper) {
Jackson2JsonRedisSerializer serializer1=new Jackson2JsonRedisSerializer(Money.class);
this.serializer = serializer1;
this.serializer.setObjectMapper(mapper);
}
@Override
public Money convert(byte[] bytes) {
return serializer.deserialize(bytes);
}
}
在设置完Converter之后还需要将两个Converter注册到RedisCustomConversions中去
- 开启对Repository的支持
@EnableRedisRepositories
@Configuration
public class RedisConfig {
@Bean
public RedisCustomConversions redisCustomConversions(ObjectMapper mapper){
return new RedisCustomConversions(
Arrays.asList(new MoneyToBytes(mapper),new BytesToMoney(mapper)));
}
@Bean(name="redisTemplate")
public RedisTemplate initRedisTemplate(RedisConnectionFactory fac, ObjectMapper obj){
Jackson2JsonRedisSerializer<Order> serializer=new Jackson2JsonRedisSerializer<>(Order.class);
serializer.setObjectMapper(obj);
RedisTemplate redisTemplate=new RedisTemplate();
redisTemplate.setConnectionFactory(fac);
redisTemplate.setKeySerializer(RedisSerializer.string());
redisTemplate.setValueSerializer(serializer);
return redisTemplate;
}
}
- 操作Repository
Redis为存储的实体类创建了一个名为(@RedisHash中的value)的集合,其中所有对象的Id都存放在其中,而每个对象以 [集合名:Id]的格式,散列Hash的类型存放在Redis中;