redisson-spring-boot-starter版本 redis spring boot_编程实战与面试指南


 

《Spring Boot编程实战与面试指南》

03-07:Spring Boot整合Redis

 


除了对关系型数据库的整合支持外,Spring Boot对非关系型数据库也提供了非常好的支持。


1、Redis介绍

Redis是一个开源(BSD许可)的、内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件,并提供多种语言的APl。Redis支持多种类型的数据结构,例如字符串(strings)、散列(hashes)、列表(lists)、集合(sets)等。同时,Redis内部内置了复本(replication)、LUA脚本(Lua scripting)、LRU驱动事件(LRU eviction)、事务(Transaction)和不同级别的磁盘持久化(persistence)。并通过Redis Sentinel和自动分区提供高可用性(high availability)。

相较于其他的key-value键值存储系统而言,Redis主要有以下优点。

1.存取速度快:Redis速度非常快,每秒可执行大约110000次的设值操作,或者执行81000次的读取操作。

2.支持丰富的数据类型:Redis支持开发人员常用的大多数数据类型,例如列表、集合、排序集和散列等。

3.操作具有原子性:所有Redis操作都是原子操作,这确保如果两个客户端并发访问,Redis服务器能接收更新后的值。

4.提供多种功能:Redis提供了多种功能特性,可用作非关系型数据库、缓存中间件、消息中间件等。


2、Redis下载安装

使用非关系型数据库Redis必须先进行安装配置并开启Redis服务,然后使用对应客户端连接使用。Redis支持多种方式的安装配置,例如Windows、Linux系统安装,Docker镜像安装等,不同安装方式的安装过程也不相同。

1.先在GiHub上下载Windows平台下的Redis安装包,在下载列表中找到对应版本的Redis,并选择对应的安装包下载即可。

下载网址:https://github.com/microsoftarchive/redis/releases

redisson-spring-boot-starter版本 redis spring boot_aitegu_02

2.下载完成后,将安装包Redis-x64-3.0.504.zip解压到自定义目录下即可,不需要进行额外配置。

redisson-spring-boot-starter版本 redis spring boot_AT阿宝哥_03


3、Redis服务配置

完成Redis的下载安装后,启动Redis服务,并使用可视化客户端工具连接对应的Redis服务进行效果测试,具体操作步骤如下。

1.开启Redis服务。

Windows下的Redis安装包解压后会有多个目录文件,包括两个重要的可执行文件:redis-server.exeredis-cli.exe。其中,redis- server.exe用于开启Redis服务,redis-cli.exe用于开启客户端工具。

这里选择双击redis-server.exe指令即可开启Redis服务。Redis服务正常启动后,同时在终端窗口显示了当前Redis版本为3.0.504和默认启动端口号为6379

redisson-spring-boot-starter版本 redis spring boot_AT阿宝哥_04

2.Redis可视化客户端工具安装连接

Redis解压目录下的redis-cli.exe指令用于开启客户端工具,不过双击这个指令打开的是终端操作界面, 对于Redis的可视化操作和查看并不方便。这里推荐使用一个Redis客户端可视化管理工具Redis Desktop Manager连接Redis服务进行管理,可以自行在https://rdm.dev/pricing官网进行下载安装。下载并安装完Redis Desktop Manager工具后,打开并连接上对应的Redis服务。

redisson-spring-boot-starter版本 redis spring boot_艾特谷_05

在Redis连接配置窗口中填写对应的连接名称Name(自定义)、连接主机Host(Redis服务地址)、连接端口Port(Redis默认端口为6379),而认证信息Auth默认情况下为空,可以不用配置;然后,单击【Test Connection】按钮进行连接测试,如果连接失败,则需要重新检查服务启动情况或者连接配置信息,如果连接成功,直接单击【OK】按钮即可完成Redis客户端连接配置。

redisson-spring-boot-starter版本 redis spring boot_goldentec_06


4、Spring Boot整合Redis

使用Spring Boot整合Redis的具体步骤如下。

4.1、创建项目

1.在Idea中通过Spring Initializr方式,创建名称为SpringBoot0307Redis的SpringBoot项目并加入如图所示依赖。本案例中Spring Boot的版本为2.4.11

redisson-spring-boot-starter版本 redis spring boot_艾特谷_07


2.项目创建成功后,其pom.xml配置如下。本案例中,添加了Spring Data Redis依赖启动器spring-boot-starter-data-redis

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.4.11</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.company</groupId>
    <artifactId>springboot0307-redis</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springboot0307-redis</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>

    <dependencies>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <scope>runtime</scope>
            <optional>true</optional>
        </dependency>
        
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

4.2、Entity类

1.编写实体类。在项目的com.company.project.model.entity包下编写AddressEntityFamilyEntityPersonEntity的实体类。

package com.company.project.model.entity;

import lombok.Data;
import org.springframework.data.redis.core.index.Indexed;

@Data
public class AddressEntity {
    
    @Indexed
    private String city;
    @Indexed
    private String country;
    
    public AddressEntity() {
    
    }
    public AddressEntity(String city, String country) {
        this.city = city;
        this.country = country;
    }
    
}
package com.company.project.model.entity;

import lombok.Data;
import org.springframework.data.redis.core.index.Indexed;

@Data
public class FamilyEntity {
    
    @Indexed
    private String type;
    @Indexed
    private String username;
    
    public FamilyEntity() {
    }
    
    public FamilyEntity(String type, String username) {
        this.type = type;
        this.username = username;
    }
    
}
package com.company.project.model.entity;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.redis.core.RedisHash;
import org.springframework.data.redis.core.index.Indexed;

import java.util.List;

@RedisHash("persons") // 指定操作实体类对象在Redis数据库中的存储空间
@Data
public class PersonEntity {
    
    @Id        // 标识实体类主键
    private String id;
    @Indexed  // 标识对应属性在Redis数据库中生成二级索引
    private String firstname;
    @Indexed
    private String lastname;
    private AddressEntity address;
    private List<FamilyEntity> familyList;
    
    public PersonEntity() {
    }
    public PersonEntity(String firstname, String lastname) {
        this.firstname = firstname;
        this.lastname = lastname;
    }
}

2.面向Redis数据库的数据操作提供了几个主要注解,这几个注解的说明如下。

  • @RedisHash("persons"):用于指定操作实体类对象在Redis数据库中的存储空间,此处表示针对Person实体类的数据操作都存储在Redis数据库中名为persons的存储空间下。
  • @ld:用于标识实体类主键。在Redis数据库中会默认生成字符串形式的HashKey表示唯一的实体对象id,当然也可以在数据存储时手动指定id。
  • @Indexed:用于标识对应属性在Redis数据库中生成二级索引。使用该注解后会在Redis数据库中生成属性对应的二级索引,索引名称就是属性名,可以方便地进行数据条件查询。

4.3、Repository接口

Spring Boot针对包括Redis在内的一些常用数据库提供了自动化配置,可以通过实现Repository接口简化对数据库中的数据进行增删改查操作,这些操作方法与Spring Data JPA操作数据的使用方法基本相同,可以使用方法名关键字进行数据操作。

1.在com.company.project.repository包下创建操作PersonEntity实体类的PersonRepository接口。

package com.company.project.repository;

import com.company.project.model.entity.PersonEntity;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;

import java.util.List;

public interface PersonRepository extends CrudRepository<PersonEntity, String> {
    List<PersonEntity> findByLastname(String lastname);
    Page<PersonEntity> findPersonByLastname(String lastname, Pageable page);
    List<PersonEntity> findByFirstnameAndLastname(String firstname, String lastname);
    List<PersonEntity> findByAddress_City(String city);
    List<PersonEntity> findByFamilyList_Username(String username);
}

4.4、配置文件

4.4.1、全局配置文件

在项目的全局配置文件application.properties中添加Redis数据库的连接配置。

########################################################
###Redis
########################################################
# Redis服务器地址
spring.redis.host=127.0.0.1
# Redis服务器连接端口
spring.redis.port=6379
# Redis服务器连接密码(默认为空)
spring.redis.password=

4.4.2、自定义配置类

com.company.project.config包下创建Redis的配置类RedisConfiguration

package com.company.project.config;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "spring.redis")
@Data
public class RedisConfiguration {
    
    private String host;
    private String port;
    private String password;
    
}

4.4.3、pom.xml

在项目的pom.xml文件中添加Redis客户端jedis的依赖。

<dependency>
     <groupId>redis.clients</groupId>
     <artifactId>jedis</artifactId>
     <version>3.5.2</version>
</dependency>

4.5、单元测试

4.5.1、测试连接Redis服务器

1.在单元测试类RedisConfigurationTest中自动装配redisConfiguration,编写create()以便测试全局配置,编写ping()方法以便测试Redis服务器连通性。

package com.company.project.config;

import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import redis.clients.jedis.Jedis;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class RedisConfigurationTest {
    
    @Autowired
    private RedisConfiguration redisConfiguration;
    
    @Test
    void create(){
        System.out.println( redisConfiguration );
    }
    
    @Test
    void ping(){
    
        Jedis jedis = new Jedis(redisConfiguration.getHost(), Integer.parseInt(redisConfiguration.getPort()));
    
        System.out.println( jedis.ping("Hello,Redis"));
        System.out.println( jedis.ping());
    }
}

2.执行create()单元测试方法,成功读取Redis的全局配置。

RedisConfiguration(host=127.0.0.1, port=6379, password=)

3.执行ping()单元测试方法,连接Redis成功。

Hello,Redis
PONG

4.5.2、测试Repository接口

1.在PersonRepository接口的单元测试类PersonRepositoryTest中编写对应的测试方法。

package com.company.project.repository;

import com.company.project.model.entity.AddressEntity;
import com.company.project.model.entity.FamilyEntity;
import com.company.project.model.entity.PersonEntity;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;

@SpringBootTest
class PersonRepositoryTest {
    
    @Autowired
    private PersonRepository repository;
    
    @Test
    public void savePerson() {
        PersonEntity person1 = new PersonEntity("李", "小明");
        PersonEntity person2 = new PersonEntity("James", "Harden");
        
        // 创建并添加住址信息
        AddressEntity address = new AddressEntity("陕西", "中国");
        person1.setAddress(address);
        
        // 创建并添加家庭成员
        List<FamilyEntity> familyEntities1 = new ArrayList<>();
        FamilyEntity dad = new FamilyEntity("父亲", "李永浩");
        FamilyEntity mom = new FamilyEntity("母亲", "王春燕");
        familyEntities1.add(dad);
        familyEntities1.add(mom);
        person1.setFamilyList(familyEntities1);
        
        // 向Redis数据库添加数据
        PersonEntity save1 = repository.save(person1);
        PersonEntity save2 = repository.save(person2);
        System.out.println(save1);
        System.out.println(save2);
    }
    
    @Test
    public void selectPerson() {
        List<PersonEntity> list = repository.findByAddress_City("陕西");
        System.out.println(list);
    }
    
    @Test
    public void updatePerson() {
        PersonEntity person = repository.findByFirstnameAndLastname("李", "小明").get(0);
        person.setLastname("小红");
        PersonEntity update = repository.save(person);
        System.out.println(update);
    }
    
    @Test
    public void deletePerson() {
        PersonEntity person = repository.findByFirstnameAndLastname("李", "小明").get(0);
        repository.delete(person);
    }
    
    
    @Test
    void findByLastname() {
    }
    
    @Test
    void findPersonByLastname() {
    }
    
    @Test
    void findByFirstnameAndLastname() {
    }
    
    @Test
    void findByAddress_City() {
    }
    
    @Test
    void findByFamilyList_Username() {
    }
}

2.运行选择PersonRepositoryTest测试类中的savePerson()方法后,打开Redis客户端可视化管理工具Rdm查看数据。

PersonEntity(id=e85557b0-c6de-47ce-a958-6e289787255d, firstname=李, lastname=小明, address=AddressEntity(city=陕西, country=中国), familyList=[FamilyEntity(type=父亲, username=李永浩), FamilyEntity(type=母亲, username=王春燕)])
PersonEntity(id=e742519b-4b41-4051-b685-f6da31531329, firstname=James, lastname=Harden, address=null, familyList=null)

redisson-spring-boot-starter版本 redis spring boot_AT阿宝哥_08


5、异常处理

5.1、MISCONF Redis is configured to save RDB snapshots…

异常信息:

Idea控制台:

...
redis.clients.jedis.exceptions.JedisDataException: MISCONF Redis is configured to save RDB snapshots, but is currently not able to persist on disk. Commands that may modify the data set are disabled. Please check Redis logs for details about the error.

...

Process finished with exit code -1

Redis Server:

[16000] 13 Oct 13:47:05.017 * 1 changes in 3600 seconds. Saving...
[16000] 13 Oct 13:47:05.029 * Background saving started by pid 9292
[9292] 13 Oct 13:47:05.120 # Failed opening the RDB file dump.rdb (in server root dir D:\Program Files\Redis-x64-3.2.100) for saving: Permission denied
[9292] 13 Oct 13:47:05.122 # rdbSave failed in qfork: Permission denied
[16000] 13 Oct 13:47:05.144 # fork operation complete
[16000] 13 Oct 13:47:05.155 # Background saving error

原因分析

MISCONF Redis配置为保存RDB快照,但目前无法在磁盘上持久化。因为权限不足,是在目录Redis根目录D:\Program Files\Redis-x64-3.2.100打开和保存dump.rdb文件。

解决方案

1.给予Users用户组在Redis根目录的完全控制权限。

redisson-spring-boot-starter版本 redis spring boot_AT阿宝哥_09

2.在Spring项目中再次运行保存数据到Redis的代码,发现Redis Server界面提示保存成功。

[16000] 13 Oct 13:50:45.061 * 1 changes in 3600 seconds. Saving...
[16000] 13 Oct 13:51:32.324 * Background saving started by pid 5672
[16000] 13 Oct 13:51:32.429 # fork operation complete
[16000] 13 Oct 13:51:32.429 * Background saving terminated with success

3.在Redis根目录中看到已新增了一个RDB快照文件dump.rdb