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

<!-- 导入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.hostlocalhostRedis 服务器主机名
spring.redis.port6379Redis 服务器端口
spring.redis.passwordRedis 服务器密码
spring.redis.timeout60s连接超时时间
spring.redis.sentinel.masterRedis 服务器主节点名称
spring.redis.sentinel.nodes哨兵节点列表,节点用“主机名:端口”表示,主机之间用逗号分割
spring.redis.sentinel.password哨兵节点密码
springredis.cluster.nodes集群节点列表,节点可以自发现,但至少要配置一个节点
spring.redis.cluster.maxRedirects5在集群中执行命令时的最大重定向次数
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);
操作描述
ClusterOperationsRedis集群的相关操作
GeoOperationsRedis地理位置的相关操作
HashOperationsRedisHash类型的相关操作
HyperLogLogOperationsRedis HyperLogLog类型的相关操作
ListOperationsRedis列表类型的相关操作
SetOperationsRedis集合类型的相关操作
StreamOperationsRedis 流的相关操作
ValueOperationsRedis值类型的相关操作
ZSetOperationsRedis有序结合类型的相关操作

在使用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的序列化器。

序列化器快捷方式说明
JdkSerializationRedisSerializerRedisSerializer.java()使用JDK的序列化方式
ByteArrayRedisSerializerRedisSerializer.byteArray()直接透传 byte[],不做任何处理
StringRedisSerializerRedisSerializer.string()根据字符集将字符串序列化为字节
GenericToStringSerializer依赖Spring的 ConversionService来序列字符串
GenericJackson2JsonRedisSerializerRedisSerializer.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;
    }

}


image.png

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

  1. 定制Money类的序列化器和反序列化器


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

  2. 有时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来识别,这些对象可以使用我们提前准备好的工具来直接操作

  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.定义接口


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

  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<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;
    }

}
  1. 操作Repository


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


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值