Spring-Redis

什么是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



    io.lettuce
    lettuce-core



    redis.clients
    jedis

导入spring-boot-starter-data-redis


    org.springframework.boot
    spring-boot-starter-data-redis
    

spring-boor-starter-data-redis默认使用lettuce作为默认客户端,且仅支持jedis和lettuce

Spring-Redis_第1张图片
如果需要使用jedis作为操作Redis客户端,需要从spring-boor-starter-data-redis中排除lettuce依赖,并导入jedis

 
            org.springframework.boot
            spring-boot-starter-data-redis
            
                
                    io.lettuce
                    lettuce-core
                
            
        
        
        
            redis.clients
            jedis
        

spring-data-redis为redis提供的抽象

Spring-Redis_第2张图片

Redis的自动配置

spring为Redis设置了配置类xxxConnectionConfiguration类(xxx=Jedis/Lettuce);
其中有两个Bean xxxClientResources (提供构建客户端所需要的资源,和配置), redisConnectionFactory(用于建立和Redis的连接)
可以使用spring-actuator来查看这两个bean
Spring-Redis_第3张图片

Spring-Redis_第4张图片

配置连接信息

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类,
Spring-Redis_第5张图片
Spring-Redis_第6张图片
SpringBoot的RedisAutoConfiguration创建了两个默认的RedisTemplate对象,stringRedisTemplate类专用于字符串类型的Redis操作,redisTemplate用于通用类型的Redis操作。
Redis中有很多种数据结构,虽然键都是字符串类型的,但是由于值的种类各不相同,所以Template为了操作这些数据结构,必须要为每种数据结构提供一个操作集,对于每一种值都需要使用操作集包含的方法来执行操作

Template中封装的操作类型

    private final ValueOperations valueOps = new DefaultValueOperations(this);
    private final ListOperations listOps = new DefaultListOperations(this);
    private final SetOperations setOps = new DefaultSetOperations(this);
    private final StreamOperations streamOps = new DefaultStreamOperations(this, new ObjectHashMapper());
    private final ZSetOperations zSetOps = new DefaultZSetOperations(this);
    private final GeoOperations geoOps = new DefaultGeoOperations(this);
    private final HyperLogLogOperations hllOps = new DefaultHyperLogLogOperations(this);
    private final ClusterOperations 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
Spring-Redis_第7张图片

于此同时,一些和Redis数据结构无关的操作直接封装到了Template类中,比如删除键,设置键的过期时间等操作;
Spring-Redis_第8张图片
Spring-Redis_第9张图片

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对象的
示例的目录

Spring-Redis_第10张图片
负责实现存放对象到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");
    }
}

运行结果
Spring-Redis_第11张图片
Spring-Redis_第12张图片
运行完成之后我们可以看到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存储
Spring-Redis_第13张图片

在现实开发中,对象的属性也可能是一个对象,这个时候只需要将对象中的子对象也设置成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;
    }

}


Spring-Redis_第14张图片

但是也不是所有的类都是我们自己定义的,能够被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类库

         
            org.joda
            joda-money
            1.0.1
            runtime
        

不做处理直接使用,可以看到下面的类中许多信息是我们不需要的,我们只想将amount属性以特定的格式保存,所以这时候就需要自定义Order的ObjectMapper
Spring-Redis_第15张图片

  1. 定制Money类的序列化器和反序列化器
    Spring-Redis_第16张图片
    Spring-Redis_第17张图片
    自定义序列化器(serializers)通常是通过Module方式注册到Jackson中,但在Spring Boot中提供了@JsonComponent注解作为替代方案,它能帮我们更为轻松的将序列化器注册到Spring Beans中。
    我们可以直接在JsonSerializer 或 JsonDeserializer类上使用 @JsonComponent注解,该注解允许我们将带该注解的类公开为Jackson序列化器或反序列化器,而无需再手动将其添加到ObjectMapper。
    Spring-Redis_第18张图片
    Spring-Redis_第19张图片

  2. 有时jackson官方也会为我们提供一个默认的Moudule注册方案,我们只需要将这个Module注册到ObjectMapper中即可,一般来说只要注册为Bean,ObjectMapper会自动注入这个Module



    com.fasterxml.jackson.datatype
    jackson-datatype-joda-money
    2.15.0

Spring-Redis_第20张图片

Spring-Redis_第21张图片

Redis的Repository操作

为了简化Spring-redis的CRUD操作,我们可以使用Redis提供的Repository操作,其中包含了对这些简单操作的逻辑的封装,直接调用这些方法来即可。Repository操作可以这样理解:我们定义了一个仓库(仓库有一个仓库的名字),仓库中有很多实体类对象,这些对象分别有一个id来识别,这些对象可以使用我们提前准备好的工具来直接操作

  1. 定义实体类
    实体类就是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.定义接口

Spring-Redis_第22张图片
3. 定制某些特殊类的转换规则
有时候这些类自动被Repository操作时得到的结果是我们不满意的,如Joda Money类的自动转换,会多出很多不需要的属性
Spring-Redis_第23张图片
这时候除了像上一章一样为JodaMoney设置序列化器和反序列化器之外,还需要设置Converter来实现对象的转换。
Spring-Redis_第24张图片
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 {

    private Jackson2JsonRedisSerializer 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 {

    private Jackson2JsonRedisSerializer 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中去

Spring-Redis_第25张图片

  1. 开启对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 serializer=new Jackson2JsonRedisSerializer<>(Order.class);
        serializer.setObjectMapper(obj);
        RedisTemplate redisTemplate=new RedisTemplate();
        redisTemplate.setConnectionFactory(fac);
        redisTemplate.setKeySerializer(RedisSerializer.string());
        redisTemplate.setValueSerializer(serializer);
        return redisTemplate;
    }

}
  1. 操作Repository

Spring-Redis_第26张图片


Redis为存储的实体类创建了一个名为(@RedisHash中的value)的集合,其中所有对象的Id都存放在其中,而每个对象以 [集合名:Id]的格式,散列Hash的类型存放在Redis中;

Spring-Redis_第27张图片
Spring-Redis_第28张图片

你可能感兴趣的:(spring,redis,bootstrap)