vlambda博客
学习文章列表

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

Chapter 3. Persistence with Spring Data and Reactive Fashion

在上一章中,我们创建了 内容管理系统CMS) 应用程序。我们还在 Spring 中引入了 RESTRepresentational State Transfer)支持,这使我们能够开发一个简单的 Web 应用程序。此外,我们还了解了依赖注入在 Spring 框架中的工作原理,这可能是该框架最著名的特性。

在本章中,我们将为我们的应用程序添加更多功能。现实世界中的系统需要将它们的数据持久化到一个真实的数据库中;这是生产就绪应用程序的基本特征。此外,根据我们的模型,我们需要选择正确的数据结构来实现性能并避免阻抗不匹配。

在本章的第一部分,我们将使用传统的 SQL 数据库作为我们应用程序的存储。我们将深入探讨 Spring Data JPA (Java Persistence API< /span>) 来实现我们的 CMS 应用程序的持久性。我们将了解如何使用这个神奇的 Spring 模块启用事务。

之后,我们将改用更现代的数据库类型,称为 NoSQLtechnologies 。在这个领域,我们将使用著名的数据库文档模型 MongoDB,然后我们将为我们的 CMS 应用程序创建最终解决方案。

MongoDB 为我们的应用程序提供了一个绝妙的解决方案,因为它支持文档存储模型,并使我们能够以 JSON 的形式存储对象,这使我们的数据更具可读性。此外,MongoDB 是无模式的,这是一个很棒的特性,因为一个集合可以存储不同的文档。这意味着记录可以有不同的字段、内容和大小。 MongoDB 的另一个重要特性是查询模型。它提供了一个易于理解的基于文档的查询,并且基于 JSON 符号,我们的查询将比任何其他数据库都更具可读性。

最后,我们将添加 Spring 5.0 中最重要的特性:对 Reactive Streams 的支持。我们的应用程序将转变为具有一些重要要求的现代 Web 应用程序。

以下是您将在本章中学习的内容的概述:

  • Implementing the Spring Data JPA
  • Creating repositories with Spring Data Reactive MongoDB
  • Learning the Reactive Spring
  • Understand the Project Reactor

Learning the basics of Docker


我们在第 1 章 Spring World 之旅中了解了 Docker 概念< /em>。现在,是时候检验我们的知识并将其付诸实践了。在本章的第一部分,我们将启动 MongoDB 和 Postgres 实例作为我们应用程序的数据库。我们将在应用程序中配置连接设置。

在本章的最后部分,我们将介绍 Maven 插件,它提供了一种通过 pom.xml  文件创建 Docker 镜像的简单方法。最后,我们将在 Docker 容器中运行我们的应用程序。 

Preparing  MongoDB

让我们创建我们的 MongoDB 容器。我们将使用 Docker Hub 提供的官方镜像。

首先,我们需要拉取镜像:

docker pull mongo:3.4.10

然后,我们将看到 Docker 引擎正在下载图像内容。

为了与我们的容器隔离,我们将为我们的应用程序和数据库创建一个分离的网络。网络应该使用桥接驱动来允许容器通信。

让我们创建一个 docker 网络

docker network create cms-application

命令输出应该是已创建网络的 ID。与我的 ID 相比,您的 ID 可能会有所不同:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

要检查网络是否创建成功,docker network ls命令可以帮助我们。

我们将启动我们的 MongoDB。网络应该是 cms-application,但我们会将数据库端口映射到主机端口。出于调试目的,我们会将 client 连接到正在运行的数据库,但请不要在非开发环境中这样做环境。

Note

在主机上公开端口不是最佳实践。 因此,我们使用 Docker 容器,其主要优点之一是进程隔离。在这种情况下,我们将无法控制网络。否则,我们可能会导致一些端口冲突。

要开始,请键入以下命令:

docker run -d --name mongodb --net cms-application -p 27017:27017 mongo:3.4.10

Note

此外,我们可以使用 docker stop mongodb 停止 Docker MongoDB 容器,并使用以下命令再次启动我们的容器: docker start mongodb

输出将是一个散列,代表容器的 ID。

参数说明如下:

  • -d: This instructs Docker to run the container in a background mode
  • --name: The container name; it will be a kind of hostname in our network
  • --net: The network where the container will be attached
  • -p: The host port and container port, which will be mapped to a container on a host interface

现在,我们的机器上运行了一个非常标准的 MongoDB 实例,我们可以开始在 CMS 应用程序中添加持久性.我们很快就会这样做。

Preparing a PostgreSQL database

与 MongoDB 一样,我们将为我们的 CMS 应用程序准备一个 PostgreSQL 实例。我们将更改我们的持久层以演示 Spring Data 如何为开发人员抽象它。然后,我们需要为此准备一个 Docker Postgres 实例。

我们将使用 Postgres 的 9.6.6 版本并使用 alpine 标签 因为< /a> 它比其他 Postgres 图像小。让我们拉出我们的图像。命令应该是这样的:

docker pull postgres:9.6.6-alpine

然后,等到下载结束。

在上一节中,我们创建了名为 cms-application 的 Docker 网络。现在,我们将在该网络上启动我们的 Postgres 实例,就像我们为 MongoDB 所做的那样。启动 Postgres 的命令应该如下:

docker run -d --name postgres --net cms-application -p 5432:5432 -e POSTGRES_PASSWORD=cms@springfive
postgres:9.6.6-alpine

参数列表与我们为 MongoDB 传递的参数列表相同。我们希望在后台模式下运行它并将其附加到我们的自定义网络。正如我们所见,docker run 命令中多了一个新参数。让我们理解它:

  • -e: This enables us to pass environment variables for a container. In this case, we want to change the password value.

好工作。我们已经完成了基础设施要求。现在让我们了解持久性细节。

Spring Data project


Spring 数据项目是一个伞形项目,它提供了一种熟悉的方式来在各种数据库技术上创建我们的数据访问层.这意味着有高级抽象可以与不同类型的数据结构进行交互,例如文档模型、列族、键值和图形。此外,Spring Data JPA 项目完全支持 JPA 规范。

这些模块为我们的领域模型提供了强大的对象映射抽象。

支持不同类型的数据结构和数据库。有一组子模块来保持框架的模块化。此外,这些子模块有两类:第一类是 Spring Framework 团队支持的项目子集,第二类是社区提供的子模块子集。

Spring 团队支持的项目包括:

  • Spring Data Commons
  • Spring Data JPA
  • Spring Data MongoDB
  • Spring Data Redis
  • Spring Data for Apache Cassandra

社区 支持的项目包括:

  • Spring Data Aerospike
  • Spring Data ElasticSearch
  • Spring Data DynamoDB
  • Spring Data Neo4J

存储库接口链的基础是 Repository 接口。它是一个标记接口,一般用途是存储类型信息。该类型将用于扩展它的其他接口。

还有一个 CrudRepository 接口。它是最重要的,名称不言自明;它提供了一些方法来执行 CRUD 操作,并提供了一些实用方法,例如 count()exists() 、 和 deleteAll()。这些是存储库实现最重要的基础接口。

Spring Data JPA

Spring Data JPA 提供了一种使用 Java EE 的 JPA 规范实现数据访问层的简单方法。通常,这些实现有很多样板和重复代码,很难维护数据库代码中的更改。 Spring Data JPA 正在尝试解决这些问题,并提供了一种无需样板和重复代码的可理解的方法。

JPA 规范提供了一个抽象层来与已实现的不同数据库供应商进行交互。 Spring 以高级模式为抽象增加了一层。这意味着 Spring Data JPA 将创建一个存储库实现并封装整个 JPA 实现 细节。我们可以使用 JPA 规范的一点知识 来构建我们的持久层。

Note

JPA 规范JCPJava 社区进程)帮助开发人员在 Java 类和关系数据库之间持久化、访问和管理数据。有一些供应商实现了这个规范。最著名的实现是 Hibernate (http://hibernate.org/orm/),默认情况下,Spring Data JPA 使用 Hibernate 作为 JPA 实现。

告别 DAO数据访问对象)模式和实施。 Spring Data JPA 旨在通过一个经过良好测试的框架和一些生产就绪的特性来解决这个问题。

现在,我们知道 Spring Data JPA 是什么了。让我们把它付诸实践。

Configuring pom.xml for Spring Data JPA

现在,我们需要将正确的 dependenciesSpring 数据 JPA。在我们的 pom.xml 文件中有几个依赖项需要配置。

 第一个 是Spring Data JPA Starter,它提供了很多自动配置类,可以让我们快速启动应用程序。 最后一个是PostgreSQL JDBC驱动,它是必需的,因为它包含了 用于连接 PostgreSQL 数据库的 JDBC 实现类。 

新的依赖项是:

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

<dependency>
  <groupId>org.postgresql</groupId>
  <artifactId>postgresql</artifactId>
  <version>42.1.4</version>
</dependency>

简单而且相当容易。

Configuring the Postgres connections

要将我们的应用程序与我们最近创建的数据库连接起来,我们需要在 application.yaml 文件中配置几行代码。再次感谢 Spring Data Starter,我们的连接将自动配置。

我们也可以使用 @Bean 注释来生成连接对象,但是有很多对象需要配置。我们将继续使用配置文件。它也更简单明了。

要配置数据库连接,我们需要向 Spring Framework 提供几个属性,例如数据库 URL、数据库用户名、密码,以及驱动程序类名称,以告知 JPA 框架有关 JDBC 类的完整路径。

application.yaml 文件应该是这样的:

spring:
  datasource:
    url: jdbc:postgresql://localhost:5432/postgres
username: postgres
password: cms@springfive
driver-class-name: org.postgresql.Driver
jpa:
    show-sql: true
generate-ddl: true

在 datasource部分,我们还配置了数据库凭据连接和数据库主机。

application.yaml 中的 JPA 部分可用于配置 JPA 框架。在这一部分中,我们配置为在控制台中记录 SQL 指令。这有助于调试和执行故障排除。此外,我们还配置了 JPA 框架,以便在应用程序启动时在数据库中创建表。

太棒了,JPA 基础设施已经配置好了。做得好!现在,我们可以将模型映射为 JPA 样式。让我们在下一节中这样做。

Mapping the models

我们已成功配置数据库连接。现在,我们已准备好使用 JPA 注释映射我们的模型。让我们从我们的 Category 模型开始。这是一个非常简单的类,这很好,因为我们对 Spring 数据 JPA 的东西感兴趣。

 Category 模型的第一个版本应该是这样的:

package springfive.cms.domain.models;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import lombok.Data;
import org.hibernate.annotations.GenericGenerator;

@Data
@Entity
@Table(name = "category")
public class Category {

@Id
  @GeneratedValue(generator = "system-uuid")
  @GenericGenerator(name = "system-uuid", strategy = "uuid2")
  String id;

String name;

}

Note

我们需要更改一些模型类以适应 JPA 规范。我们可以在 GitHub 上找到模型类: https://github.com/PacktPublishing/Spring-5.0-By-Example/tree/master/Chapter03/cms-postgres/src/ main/java/springfive/cms/domain/models.

这里有一些新东西。 @Entity 注释指示 JPA 框架被注释的类是一个实体, 在我们的例子中, Category 类,然后框架会将其与数据库表相关联。 @Table 注解用于命名数据​​库中的表。这些注释被插入到类级别,这意味着在类声明的顶部。

@Id 注释指示 JPA 哪个 annotated 字段是数据库表的主键。为实体按顺序生成 ID 不是一个好习惯,尤其是在创建 API 时。它可以帮助黑客了解有关 ID 的逻辑并使攻击更容易。因此,我们将生成 UUID(通用唯一标识符)而不是简单的顺序 ID。 @GenericGeneratorannotation 指示 Hibernate(JPA 规范实现供应商)生成随机 UUID。

Adding the JPA repositories in the CMS application

完成整个 infrastructure 和 JPA 映射后,我们可以将存储库添加到我们的项目中。在Spring Data项目中,有一些抽象,如 RepositoryCrudRepositoryJpaRepository。我们将使用 JpaRepository 因为它支持分页和排序功能。

我们的存储库将非常简单。有几个标准方法,例如 save()update()delete(),我们将看看一些 DSL 查询方法,这些方法允许开发人员根据属性名称创建自定义查询。 我们创建了 an AbstractRepository< /code> 帮助我们将对象存储在内存中。不再需要了。我们可以删除它。

让我们创建我们的第一个 JPA 存储库:

package springfive.cms.domain.repository;

import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import springfive.cms.domain.models.Category;

public interface CategoryRepository extends JpaRepository<Category, String> {

  List<Category> findByName(String name);

List<Category> findByNameIgnoreCaseStartingWith(String name);

}

正如我们所见,JpaRepository 接口的类型是所需的实体以及实体的 ID 类型。这部分没有秘密。这个神奇的事情恰好支持基于属性名称的自定义查询。在 Category 模型中,有一个名为 name 的属性。 我们可以使用 Category 模型属性在我们的 CategoryRepository 中创建自定义方法,使用 指令。

如我们所见,在 findByName(String name) 之上,Spring Data Framework 将创建正确的查询来按名称查找类别。太棒了。

自定义查询方法支持的关键字有很多:

逻辑关键字

逻辑表达式

AND

之后

After, IsAfter

之前

Before, IsBefore

包含

包含, IsContaining, 包含

BETWEEN

Between, IsBetween

ENDING_WITH

EndingWith, IsEndingWith, EndsWith

存在

存在

False, IsFalse

GREATER_THAN

GreaterThan, IsGreaterThan

GREATHER_THAN_EQUALS

GreaterThanEqual, IsGreaterThanEqual

IN

In, IsIn

IsEquals,(或无关键字)

IS_EMPTY

IsEmpty, Empty

IS_NOT_EMPTY

IsNotEmpty, NotEmpty

IS_NOT_NULL

NotNull, IsNotNull

IS_NULL

Null, IsNull

LESS_THAN

LessThan, IsLessThan

LESS_THAN_EQUAL

LessThanEqual, IsLessThanEqual

喜欢

喜欢, IsLike

, IsNear

不是

Not, IsNot

NOT_IN

NotIn, IsNotIn

NOT_LIKE

NotLike, IsNotLike

正则表达式

正则表达式, MatchesRegex, 匹配项

STARTING_WITH

StartingWith, IsStartingWith, StartsWith

True, IsTrue

WITHIN

Within, IsWithin

有很多方法可以创建基于属性名称的查询。我们也可以使用关键字来组合关键字,例如 findByNameAndId。 Spring Data JPA 提供了一种一致的方式来创建查询。

Configuring transactions

当我们使用 JPA 规范时,大多数 应用程序 也需要支持事务。即使在其他模块中,Spring 也对事务具有出色的支持。这种支持与 Spring Data JPA 集成在一起,我们可以利用它。在 Spring 中配置事务是小菜一碟;我们需要在需要时插入 @Transactional 注释。有一些不同的用例可以使用它。我们将在服务层中使用 @Transactional,然后将注解放入我们的服务类中。让我们看看我们的 CategoryService 类:

package springfive.cms.domain.service;

import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import springfive.cms.domain.exceptions.CategoryNotFoundException;
import springfive.cms.domain.models.Category;
import springfive.cms.domain.repository.CategoryRepository;
import springfive.cms.domain.vo.CategoryRequest;

@Service
@Transactional(readOnly = true)
public class CategoryService {

private final CategoryRepository categoryRepository;

  public CategoryService(CategoryRepository categoryRepository) {
this.categoryRepository = categoryRepository;
}

@Transactional
public Category update(Category category) {
return this.categoryRepository.save(category);
}

@Transactional
public Category create(CategoryRequest request) {
    Category category = new Category();
category.setName(request.getName());
    return this.categoryRepository.save(category);
}

@Transactional
public void delete(String id) {
final Optional<Category> category = this.categoryRepository.findById(id);
category.ifPresent(this.categoryRepository::delete);
}

public List<Category> findAll() {
return this.categoryRepository.findAll();
}

public List<Category> findByName(String name) {
return this.categoryRepository.findByName(name);
}

public List<Category> findByNameStartingWith(String name) {
return this.categoryRepository.findByNameIgnoreCaseStartingWith(name);
}

public Category findOne(String id) {
final Optional<Category> category = this.categoryRepository.findById(id);
    if (category.isPresent()) {
return category.get();
} else {
throw new CategoryNotFoundException(id);
}
  }

}

CategoryService 类中有很多 @Transactional 注解。类级别的第一个注解指示框架为这些类中存在的所有方法配置 readOnly,除了使用 @Transactional 配置的方法代码>。在这种情况下,类级别的注释将被 readOnly=false 覆盖。这是省略该值时的默认配置。

Installing and configuring pgAdmin3

为了连接我们的 PostgreSQL 实例,我们将使用 pgAdmin 3,它是 Postgres 团队提供 的免费工具。

要安装pgAdmin 3,我们可以使用以下 命令:

sudo apt-get install pgadmin3 -y

这将在我们的机器上安装 pgAdmin 3。

安装后,打开 pgAdmin 3 然后点击 Add a connection to a server。该按钮如下所示

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

然后,填写信息,如下图所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

密码应该是: cms@springfive.

太棒了,我们的 pgAdmin 3 工具已经配置好了。

Checking the data on the database structure

整个应用程序结构已准备就绪。现在,我们可以检查数据库以获取我们的持久数据。有许多开源 Postgres 客户端。我们将使用之前配置的 pgAdmin 3。

第一次打开应用程序时,系统会询问您有关凭据和主机的信息。我们必须在 application.yaml 文件中放置我们配置的相同信息。然后,我们就可以在数据库中进行指令了。

在检查数据库之前,我们可以使用 Swagger 在我们的 CMS 系统中创建一些类别。我们可以使用第 2 章中提供的说明,从 Spring World 开始 - CMS 应用程序, 来创建一些数据。

之后,我们可以在数据库中执行以下 SQL指令:

select * from category;

结果应该是在 Swagger 调用上创建的类别。在我的例子中,我创建了两个类别, sportsmovies。 结果会像那些如以下屏幕截图所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

很棒的工作,伙计们。该应用程序已完全运行。

现在,我们将为存储库创建最终解决方案。我们已经了解了 Spring Data 项目的基础知识,在下一节中,我们会将持久层更改为现代数据库。

Creating the final data access layer


我们已经玩过 Spring Data JPA 项目,并且我们已经看到它是多么容易。我们学习了如何配置数据库连接以将真实数据持久保存在 Postgres 数据库中。现在,我们将为我们的应用程序的数据访问层创建最终解决方案。最终的解决方案将使用 MongoDB 作为数据库,并将使用 Spring Data MongoDB 项目,该项目为 MongoDB 存储库提供支持。

我们将看到与 Spring Data JPA 项目的一些相似之处。这很神奇 因为我们可以在实践中证明 Spring Data 抽象的力量。通过一些更改,我们可以转移到另一个数据库模型。

让我们了解这个新项目并将其付诸实践。

Spring Data MongoDB

Spring Data MongoDB 提供与我们的域对象和 MongoDB 文档的集成。有了几个注释,我们的实体类就可以保存在数据库中了。 mapping 基于 POJO (Plain Old Java Object) 模式,所有 Java 开发人员都知道。

模块提供了两个抽象级别。第一个是高级抽象。它提高了开发人员的生产力。此级别提供了几个注释来指示框架转换 MongoDB 文档中的域对象,反之亦然。开发者不需要写任何关于持久化的代码;它将由 Spring Data MongoDB 框架管理。这一层还有更多令人兴奋的东西,比如 Spring Conversion Service 提供的丰富的映射配置。 Spring Data 项目提供了丰富的 DSL,使开发人员能够基于属性名称创建查询。

第二级抽象是低级抽象。在此级别,行为不会由框架自动管理。开发人员需要更多地了解 Spring 和 MongoDB 文档模型。该框架提供了几个接口,使开发人员能够控制读取和写入指令。这对于高级抽象不太适合的场景很有用。在这种情况下,控件在实体映射中应该更细化。

同样,Spring 提供 开发人员的选择权。高级抽象提高了开发人员的性能,而低级抽象允许开发人员进行更多控制。

现在,我们将为模型添加映射注释。我们开始做吧。

Removing the PostgreSQL and Spring Data JPA dependencies

我们将转换我们的 project 以使用全新的 Spring Data Reactive MongoDB 存储库。之后,我们将不再使用 Spring Data JPA 和 PostgreSQL 驱动程序。让我们从 pom.xml 中删除这些依赖项:

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

<dependency>
  <groupId>org.postgresql</groupId>
  <artifactId>postgresql</artifactId>
  <version>42.1.4</version>
</dependency>

然后,我们可以添加以下依赖项:

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

Mapping the domain model

我们将在我们的域模型上添加映射 annotations。 Spring Data MongoDB 将使用这些注释将我们的对象持久保存在 MongoDB 集合中。我们将从 Category entity 开始,它应该是这样的:

package springfive.cms.domain.models;

import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Data
@Document(collection = "category")
public class Category {

@Id
  String id;

  String name;

}

我们在 Category 类中添加了两个新注释。 Spring Data MongoDB 中的 @Document 使我们能够配置集合名称。 MongoDB 中的集合类似于 SQL 数据库中的表。

@Id 注释来自 Spring Data Commons 项目。这很有趣,因为正如我们所见,它并不特定于 MongoDB 映射。带有此的字段注释将在 MongoDB 集合上的 _id 字段中进行转换。 

有了这几个 注解,Category 类被配置为在 MongoDB 上持久化。在下一节中,我们将创建我们的存储库类。

我们需要为我们的其他实体执行相同的任务。 UserNews 需要像我们为 Category 做的那样配置 类。完整的源代码可以在 GitHub 上找到: https://github.com/PacktPublishing/Spring-5.0-By-Example/tree/master/Chapter03/cms -mongo-non-reactive/src/main/java/springfive/cms/domain/models.

Configuring the database connection

在创建存储库之前,我们将配置 MongoDB 连接。存储库层抽象了驱动程序的实现,但必须正确配置驱动程序。

在资源目录中,我们将更改之前为 Spring Data JPA 配置的 application.yaml 文件。 Spring Framework 支持通过 YAML 文件进行配置。这种文件对人类来说更具可读性,并且具有一种层次结构。这些功能是选择此扩展程序的原因。

application.yaml 文件应类似于以下示例:

spring:
  data:
    mongodb:
      database: cms
      host: localhost
      port: 27017

该文件现在非常简单。有一个 database 标签用于配置数据库名称。  hostport 标签是关于 MongoDB 实例正在运行的地址。

我们还可以使用几个对象以编程方式配置连接,但这需要我们编写大量样板代码。 Spring Boot 为我们提供了开箱即用的功能。让我们尽情享受吧。

太好了,连接配置成功。基础设施要求得到解决。让我们继续实现我们的存储库。

Note

Spring Boot Framework 支持 application.propertiesapplication.yaml 中的配置文件。这意味着如果应用程序以属性文件样式配置,我们可以使用 application-<profile>.properties。然后,这些属性将应用于所需的配置文件。在 YAML 样式中,我们只能使用具有多个配置文件的一个文件。

Adding the repository layer

一旦实体被映射,并且 connections 完成,就该创建我们的存储库了。 Spring Data Framework 提供了一些可用于不同用例的接口。我们将使用 MongoDB 数据库的特化,即 MongoRepository。它扩展了 PagingAndSortingRepository 和 QueryByExampleExecutor。首先是关于分页排序 特性,另一个是关于示例查询。

Note

在某些情况下,数据库查询结果集可能非常大。这可能会导致一些应用程序性能问题,因为我们将获取大量数据库记录。我们可以限制从数据库中获取的记录数量并为此配置限制。这种技术称为分页。我们可以在 Spring Data Commons Documentation (https://docs.spring.io/spring-data/commons/docs/current/reference/html/ )。

为了方便起见,该接口提供了许多内置方法。有几种方法可以插入一个或多个实例,列出请求实体的所有实例的方法,删除一个或多个实例的方法,以及更多功能,例如排序和分页。

它使开发人员无需代码甚至无需深入了解 MongoDB 即可创建存储库。但是,要解决各种错误,需要一些 MongoDB 知识。

我们将从创建 CategoryRepository 开始。将 CategoryRepository 的类型改为接口而不是类。此接口中的代码不是必需的。 Spring 容器将在应用程序启动时注入正确的实现。

让我们创建我们的第一个具体存储库,这意味着该存储库将把数据保存在我们之前配置的 MongoDB 上。 CategoryRepository 需要是这样的:

package springfive.cms.domain.repository;

import org.springframework.data.mongodb.repository.MongoRepository;
import springfive.cms.domain.models.Category;

public interface CategoryRepository extends MongoRepository<Category,String> {}

类型是 interface 存储库不再有任何刻板印象。  Spring 容器可以识别实现,因为它扩展了 MongoRepository 接口。

MongoRepository 接口应该被参数化。第一个参数是它所代表的模型类型。在我们的例子中,它代表 Category 类的存储库。第二个参数是关于模型ID的类型。我们将为此使用字符串类型。

现在,我们需要对其他实体 UserNews 执行相同的操作。该代码与前面的代码非常相似。您可以在 GitHub 上找到完整的源代码: https://github.com/PacktPublishing/Spring-5.0-By-Example/tree/master/Chapter03/cms -mongo-non-reactive/src/main/java/springfive/cms/domain/repository.

在下一节中,我们将检查数据库以断言这些行已正确持久化。 

Checking the persistence

现在,我们可以测试 persistence 和应用程序的所有层。我们将为此提供 API 文档。让我们打开 Swagger 文档并在我们的 CMS 应用程序中创建一些记录。

在 Swagger 上创建示例类别:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

填写 JSON 类别,如上图所示,然后点击 Try it out!。它将调用 Category API 并将类别持久保存在数据库中。现在,我们可以检查一下。

要连接到 MongoDB 实例并检查集合,我们将使用 mongo-express 工具。它是一个用 NodeJS 编写的基于 Web 的工具,用于与我们的数据库实例进行交互。

该工具可以安装,但我们将在 Docker 容器上运行该工具。 Docker 工具将帮助我们这部分。让我们启动容器:

docker run -d --link mongodb:mongo--net cms-application -p 8081:8081 mongo-express

它指示 Docker 使用 mongo-express 工具启动容器并连接到所需的实例。 --link 参数指示 Docker 为我们的 MongoDB 实例创建一种 hostname。记住我们实例的名字是mongodb;我们之前在运行命令上做过。

好工作。转到 http://localhost:8081 我们会看到这个页面:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

有几个数据库。我们对 CMS 数据库很感兴趣。点击cms旁边的 查看按钮。然后,该工具将呈现所选数据库的集合;在我们的例子中,CMS 数据库。视图应该是这样的:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

该类别显示为一个集合。我们可以ViewExport,导出为JSON,但是现在,我们有兴趣检查我们的 CMS 应用程序是否正确保存了数据。所以,点击 View 按钮。我们将像这样使用 MongoDB 集合数据:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

正如我们所见,数据按预期存储在 MongoDB 中。数据库中有两个类别——sports 和travel。有一个 _class 字段可以帮助 Spring Data 转换域类。

很棒的工作,CMS 应用程序已启动并运行,并且还在 MongoDB 中保存数据。现在,我们的应用程序几乎已准备好生产,并且数据保存在令人惊叹的文档数据存储中。

在下一节中,我们将创建 Docker 映像,然后我们将使用 Docker 命令运行 C​​MS 应用程序。这会很有趣。

Creating the Docker image for CMS


我们正在做一项了不起的工作。我们使用 Spring Boot创建了应用程序"> 框架。该应用程序一直在使用 Spring REST、Spring Data 和 Spring DI。

现在我们将向前迈出一步,创建我们的 Docker 镜像。这将有助于我们交付生产应用程序。有一些优势,我们可以在本地或任何云提供商上运行应用程序,因为 Docker 抽象了操作系统层。我们不需要在应用程序主机上安装 Java,它还允许我们在主机上使用不同的 Java 版本。采用 Docker 进行交付有很多优势。 

我们使用 Maven 作为构建工具。 Maven 有一个优秀的插件来帮助我们创建 Docker 镜像。在下一节中,我们将了解 Maven 如何帮助我们。

Configuring the docker-maven-plugin

提供了 fabric8 (https://github.com/fabric8io /docker-maven-plugin)。它是在 Apache-2.0 许可下获得许可的,这意味着我们可以毫无顾虑地使用它。

我们将配置我们的项目以使用它,并且在创建镜像之后,我们将把这个镜像推送到 Docker Hub 上。它是一个公共 Docker 注册表。 

步骤是:

  1. Configure the plugin
  2. Push the Docker image
  3. Configure the Docker Spring profile

然后,就是表演时间了。我们走吧。

Adding the plugin on pom.xml

让我们配置 Maven 插件。需要在我们的 pom.xml 的 plugin 部分添加一个插件并添加一些配置。该插件应配置如下:

<plugin>
   <groupId>io.fabric8</groupId>
   <artifactId>docker-maven-plugin</artifactId>
   <version>0.21.0</version>
   <configuration>
      <images>
         <image>
<name>springfivebyexample/${project.build.finalName}</name> 
            <build>
               <from>openjdk:latest</from>
<entryPoint>java -Dspring.profiles.active=container -jar /application/${project.build.finalName}.jar</entryPoint>
               <assembly>
<basedir>/application</basedir>
                  <descriptorRef>artifact</descriptorRef>
                  <inline>
                     <id>assembly</id>
                     <files>
                        <file>
<source>target/${project.build.finalName}.jar</source>
                        </file>
                     </files>
                  </inline>
               </assembly>
               <tags>
                  <tag>latest</tag>
               </tags>
               <ports>
<port>8080</port>
               </ports>
            </build>
            <run>
               <namingStrategy>alias</namingStrategy>
            </run>
            <alias>${project.build.finalName}</alias>
         </image>
      </images>
   </configuration>
</plugin>

这里有几个新配置。让我们从 <name> 标记开始——它配置存储库和 Docker 映像名称以推送到 Docker Hub。对于本书,我们将使用 springfivebyexample 作为 Docker ID。 我们可以看到有一个 slash 作为存储库和图像名称的分隔符。我们的图像名称将是最终的项目名称。然后,我们需要对其进行配置。

Note

Docker ID是免费使用的,可以用来访问一些Docker服务,比如Docker Store、Docker Cloud、Docker Hub。我们可以在 Docker 页面 (https://docs.docker.com/ docker-id/)。

此配置应与以下代码片段中所示的相同:

<build>
<finalName>cms</finalName>
  ....
</build>

另一个重要的标签是 <entrypoint>。这是我们使用 docker run命令时的exec系统调用 指令。在我们的例子中,我们希望应用程序在容器引导时运行。我们将执行 java -jar 将容器作为 Spring 的活动配置文件传递。

我们需要传递 Java 工件的完整路径。此路径将在 <basedir> 参数的 <assembly> 标记上配置。它可以是任何文件夹名称。此外,还有对 Java 工件路径的配置。通常,这是编译结果的目标文件夹。可以在 <source>标签中配置。

最后,我们有了 <port> 配置。应用程序的端口将使用此标记公开。

现在,我们将使用以下指令创建一个 Docker 镜像:

mvn clean install docker:build

它应该在项目的根文件夹中执行。  docker:build 命令的目标是为我们的项目构建一个 Docker 镜像。构建结束后,我们可以检查 Docker 镜像是否创建成功。

然后,键入以下命令:

docker images

 springfivebyexample/cms 图像应该存在,如以下屏幕截图所示:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

好的。图像已准备就绪。让我们推送到 Docker Hub。

Pushing the image to Docker Hub

Docker Hub 是一个用于存储 Docker 镜像的公共存储库。它是免费的,我们将在本书中使用它。现在,我们将把我们的镜像推送到 Docker Hub 注册表。

这个命令很简单。类型:

docker push springfivebyexample/cms:latest

Note

我使用了我创建的 springfivebyexample 用户。您可以在 Docker Hub 上测试您自己的用户创建的 docker push 命令,并在 docker push 命令上更改用户。您可以在 Docker Hub (https://cloud.docker.com/)。

然后,图像将被发送到注册表。这就对了。

Note

我们可以在 Docker Hub (https://store.docker .com/community/images/springfivebyexample/cms)。如果您使用了自己的用户,则链接可能会更改。

Configuring the Docker Spring profile

在我们在 Docker 容器中运行我们的应用程序之前,我们需要创建一个 YAML 文件来配置容器配置文件。新的 YAML 文件应该命名为 application-container.yaml 因为我们将使用容器配置文件来运行它。请记住,我们在上一节中在 pom.xml 上配置了 entrypoint

让我们创建我们的新文件。该文件应与以下代码段中描述的内容相同:

spring:
  data:
    mongodb:
      database: cms
      host: mongodb
      port: 27017

必须为 MongoDB 更改主机。我们已经在 准备 MongoDB 部分中运行了具有此名称的 MongoDB 容器。这是一个重要的配置,这时候我们需要注意。我们不能再使用 localhost 因为应用程序现在在 Docker 容器中运行。该上下文中的 localhost 意味着它在同一个容器中,并且我们在 CMS 应用程序容器中没有 MongoDB。我们需要每个容器有一个应用程序,并避免一个容器的多重责任。

完毕。在下一节中,我们将在 Docker 容器中运行我们的第一个应用程序。将会很精彩。我们开始做吧。

Running the Dockerized CMS

在上一节中,我们创建了文件以正确配置 容器配置文件。现在,是时候运行我们的容器了。命令很简单,但是需要注意参数。 

我们运行的指令应该和下面的代码一样:

docker run -d --name cms --link mongodb:mongodb --net cms-application -p 8080:8080 springfivebyexample/cms:latest

我们一直在设置 MongoDB 容器的链接。请记住,我们在 YAML 文件的 host 属性中进行了此配置。在引导阶段,应用程序将查找名为 mongodb 的 MongoDB 实例。我们通过使用链接命令解决了这个问题。它将完美地工作。

我们可以使用 docker ps 命令检查我们的 application 是否健康.输出应该是这样的:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

在第一行,我们有我们的应用程序容器。它已启动并正在运行。

很棒的工作。我们的应用程序是完全容器化的,可以随时部署在我们想要的任何地方。

Putting in Reactive fashion


我们一直在使用 Spring Boot 创建一个惊人的应用程序。该应用程序建立在 Spring Framework 上的传统 Web 堆栈之上。这意味着应用程序使用基于 Servlet API 的 Web 服务器。 

servlet 规范是使用阻塞语义或每线程一个请求模型构建的。有时,由于非功能性需求,我们需要更改应用程序架构。例如,如果该应用程序被一家大公司收购,并且该公司想制定一个面向全世界启动该应用程序的计划,那么请求量可能会增加很多。因此,我们需要改变架构以适应云环境的应用程序结构。

通常,在云环境中,机器比传统数据中心要小。代替大型机器,使用许多小型机器并尝试水平扩展应用程序很流行。在这种情况下,servlet 规范可以切换到基于 Reactive Streams 创建的架构。这种架构比 servlet 更适合云环境。

Spring Framework 一直在创建 Spring WebFlux 来帮助开发人员创建响应式 Web 应用程序。让我们将我们的应用程序架构更改为响应式并学习全新的 Spring WebFlux 组件。

Reactive Spring

Reactive Stream Spec 是为流处理异步编程提供标准的规范。现在在编程界越来越流行,Spring在框架上引入了它。 

这种编程风格在资源使用方面更加高效,并且非常适合具有多核的新一代机器。

Spring reactive 使用 Project Reactor 作为 Reactive Streams 的实现。 Project Reactor 由 Pivotal 提供支持,并且很好地实现了 Reactive Streams Spec。

现在,我们将深入研究 Spring Boot 的响应式模块,并创建一个令人惊叹的响应式 API,并尝试 Spring 框架的新风格。 

Project Reactor

Project Reactor 由 Spring 和 Pivotal 团队创建。这个项目是 Reactive Streams for JVM 的一个实现。 它是一个完全非阻塞的基础,可帮助开发人员在 JVM 生态系统中创建非阻塞应用程序。

在我们的应用程序中使用 Reactor 是有限制的。该项目在 Java 8 及更高版本上运行。这很重要,因为我们将在示例和项目中使用许多 lambda 表达式。

Spring Framework 在内部使用 Project Reactor 作为 Reactive Streams 的实现。

Components

让我们看看 Project Reactor 的不同组件:

  • Publishers: The publishers are responsible for pushing data elements to the stream. It notifies the subscribers that a new piece of data is coming to the stream.The publisher interface is defined in the following code snippet:
/************************************************************************
 * Licensed under Public Domain (CC0)                                    *
 *                                                                       *
 * To the extent possible under law, the person who associated CC0 with  *
 * this code has waived all copyright and related or neighboring         *
 * rights to this code.                                                  *
 *                                                                       *
 * You should have received a copy of the CC0 legalcode along with this  *
 * work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.*
 ************************************************************************/

package org.reactivestreams;

/**
 * A {@link Publisher} is a provider of a potentially unbounded number of sequenced elements, publishing them according to
 * the demand received from its {@link Subscriber}(s).
 * <p>
* A {@link Publisher} can serve multiple {@link Subscriber}s subscribed {@link #subscribe(Subscriber)} dynamically
 * at various points in time.
 *
 * @param <T> the type of element signaled.
 */
public interface Publisher<T> {

public void subscribe(Subscriber<? super T> s);

}
  • Subscribers: The subscribers are responsible for making the data flow in the stream. When the publisher starts to send the piece of data on the data flow, the piece of data will be collected by the onNext(T instance) method, which is the parametrized interface. The subscriber interface is defined in the following code snippet:
/************************************************************************
 * Licensed under Public Domain (CC0)                                    *
 *                                                                       *
 * To the extent possible under law, the person who associated CC0 with  *
 * this code has waived all copyright and related or neighboring         *
 * rights to this code.                                                  *
 *                                                                       *
 * You should have received a copy of the CC0 legalcode along with this  *
 * work. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.*
 ************************************************************************/

package org.reactivestreams;

/**
 * Will receive call to {@link #onSubscribe(Subscription)} once after passing an instance of {@link Subscriber} to {@link Publisher#subscribe(Subscriber)}.
 * <p>
* No further notifications will be received until {@link Subscription#request(long)} is called.
 * <p>
* After signaling demand:
 * <ul>
* <li>One or more invocations of {@link #onNext(Object)} up to the maximum number defined by {@link Subscription#request(long)}</li>
* <li>Single invocation of {@link #onError(Throwable)} or {@link Subscriber#onComplete()} which signals a terminal state after which no further events will be sent.
 * </ul>
* <p>
* Demand can be signaled via {@link Subscription#request(long)} whenever the {@link Subscriber} instance is capable of handling more.
 *
 * @param <T> the type of element signaled.
 */
public interface Subscriber<T> {

public void onSubscribe(Subscription s);

public void onNext(T t);

public void onComplete();
}
Hot and cold

reactive 序列有两类——热和冷。这些功能直接影响实现的使用。因此,我们需要了解它们:

  • Cold: The cold publishers start to generate data only if it receives a new subscription. If there are no subscriptions, the data never comes to the flow.
  • Hot: The hot publishers do not need any subscribers to generate the data flow. When the new subscriber is registered, the subscriber will only get the new data elements emitted.
Reactive types

有两种 reactive 类型表示反应序列。  Mono 对象表示单个值或空 0|1。  Flux 对象 表示一个由 0|N 个项目组成的序列。

我们会在我们的代码中找到很多引用。 Spring Data 响应式存储库在其方法中使用这些抽象。  findOne() 方法返回 Mono  对象和  findAll() 返回一个  Flux<T>。我们将在 REST 资源中找到相同的行为。

Let's play with the Reactor

为了更好地理解它,让我们来玩一下 Reactor。我们会在实践中落实和理解冷热发布者的区别。 

发​​布商不产生任何数据直到新的订阅到来。在下面的代码中,我们将创建一个冷发布者,并且 System.out:println 永远不会执行 因为它没有任何订阅者。让我们测试一下行为:

@Test
public void coldBehavior(){
  Category sports = new Category();
sports.setName("sports");
Category music = new Category();
sports.setName("music");
Flux.just(sports,music)
      .doOnNext(System.out::println);
}

正如我们所见,方法 subscribe() 没有出现在这个片段中。当我们执行代码时,我们不会在标准打印输出中看到任何数据。

我们可以在 IDE 上执行该方法。我们将能够看到这个测试的输出。输出应该是这样的:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

该过程已完成,测试通过,我们将无法看到打印。那是冷出版商的行为。

现在,我们将订阅发布者,数据将在数据流上发送。让我们试试这个。

我们将在 doOnNext() 之后插入订阅指令。让我们改变我们的代码:

 @Test
  public void coldBehaviorWithSubscribe(){
    Category sports = new Category();
    sports.setId(UUID.randomUUID().toString());
    sports.setName("sports");
    Category music = new Category();
    music.setId(UUID.randomUUID().toString());
    music.setName("music");
    Flux.just(sports,music)
        .doOnNext(System.out::println)
        .subscribe();
  }

输出应该是这样的:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

在前面的截图中,我们可以看到发布者在流被订阅后将数据推送到流上。那就是订阅后的冷发布者行为。

热门发布者不依赖任何订阅者。 hot 发布者将发布数据,即使没有订阅者接收数据。让我们看一个例子:

@Test
public void testHotPublisher(){
  UnicastProcessor<String> hotSource = UnicastProcessor.create();
Flux<Category> hotPublisher = hotSource.publish()
      .autoConnect().map((String t) -> Category.builder().name(t).build());
hotPublisher.subscribe(category -> System.out.println("Subscriber 1: "+ category.getName()));
hotSource.onNext("sports");
hotSource.onNext("cars");
hotPublisher.subscribe(category -> System.out.println("Subscriber 2: "+category.getName()));
hotSource.onNext("games");
hotSource.onNext("electronics");
hotSource.onComplete();
}

让我们了解这里发生了什么。 UnicastProcessor 是一个只允许一个 Subscriber 的处理器。当订阅者请求时,处理器会重播通知。它将在流上发出一些数据。正如我们将看到的,第一个订阅将捕获所有类别,因为它是在事件发射之前注册的。第二个订阅将仅捕获最后的事件,因为它是在最后两次发射之前注册的。 

 

上述代码的输出应该是:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

惊人的。这是热门发布者的行为。

Spring WebFlux

传统的 Java 企业 Web 应用程序 基于 servlet 规范。 3.1 之前的 servlet 规范是同步的,这意味着它是使用阻塞语义创建的。这种模式在当时很好,因为计算机很大,具有强大的 CPU 和数百 GB 的内存。通常,当时的应用程序都配置了一个包含数百个线程的大型线程池,因为计算机就是为此而设计的。当时的主要部署模型是副本。有一些机器具有相同的配置和应用程序部署。

开发人员多年来一直在创建这样的应用程序。

如今,大多数应用程序都部署在云供应商中。没有大机器了,因为价格要高得多。不是大型机器,而是许多小型机器。它便宜得多,而且这些机器具有合理的 CPU 功率和内存。 

在这个新场景中,拥有巨大线程池的应用程序不再有效,因为机器很小,它没有能力处理所有这些线程。

Spring 团队在框架中添加了对 Reactive Streams 的支持。这种编程模型改变了应用程序的部署和构建应用程序的方式。

应用程序不是使用每个请求线程模型,而是使用事件循环模型创建的。此模型需要少量线程,并且在资源使用方面更有效。

Event-loop model

这个model被NodeJS语言普及,基于事件驱动编程。有两个中心概念:将在队列中排队的事件,以及跟踪和处理这些事件的处理程序。

采用这种模式有一些优点。第一个是排序。事件按事件到来的相同顺序排队和分派。在某些用例中,这是一个重要的要求。

另一个是同步。事件循环必须仅在一个线程上执行。这使得状态易于处理并避免了共享状态问题。

这里有一条重要的建议。处理程序不能是同步的。否则,应用程序将被阻塞,直到处理程序结束其工作负载。

Spring Data for Reactive Extensions

Spring 数据项目有一些扩展可以使用响应式基础。该项目提供了几个基于异步编程的实现。这意味着整个堆栈是异步的,因为数据库驱动程序也是如此。

Spring 反应式存储库支持 Cassandra、MongoDB 和 Redis 作为数据库存储。存储库实现提供与非响应式实现相同的行为。有一个 DSL (Domain-Specific Language) 来创建特定领域的查询方法。

该模块使用 Project Reactor 作为响应式 foundation 实现,但也可以将实现更改为 RxJava。这两个库都是生产就绪的,并被社区采用。需要注意的一点是,如果我们更改为 RxJava,我们需要确保我们的方法返回到 ObservableSingle

Spring Data Reactive

Spring 数据项目支持响应式数据访问。到目前为止,Spring 已经支持 MongoDB、Apache Cassandra 和 Redis,它们都有响应式驱动程序。

 

在我们的 CMS 应用程序中,我们将使用 MongoDB 响应式驱动程序为我们的存储库提供响应式特性。我们将使用 Spring Data 反应式提供的新反应式接口。另外,我们需要稍微修改一下代码。在本章中,我们将一步一步地做到这一点。开始吧。

Reactive repositories in practice

在开始之前,我们可以在 GitHub 上查看 full 源代码,也可以执行以下步骤。

现在,我们已准备好构建新的响应式存储库。我们需要做的第一件事是将 Maven 依赖项添加到我们的项目中。这可以使用 pom.xml 来完成。

让我们配置我们的新依赖项:

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

我们的项目已准备好使用响应式 MongoDB 存储库。

Creating the first Reactive repository

我们的 CMS 项目中有几个存储库。现在,我们需要 将这些存储库转换为响应式存储库。我们要做的第一件事是从 CrudRepository 中删除扩展,这不再是必需的了。现在,我们想要它的响应式版本。 

我们将更新 ReactiveMongoRepository 接口。接口的参数和我们之前插入的一样。界面应该是这样的:

package springfive.cms.domain.repository;

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import springfive.cms.domain.models.Category;

public interface CategoryRepository extends ReactiveMongoRepository<Category,String> {
}

这与我们之前创建的非常相似。我们需要扩展新的 ReactiveMongoRepository 接口, 其中包含用于 CRUD 操作的方法等等。接口返回 Mono Flux 。 这些方法不再返回实体。在采用 Reactive Stream 时,这是一种常见的编程方式。

我们还需要更改其他存储库。您可以在 GitHub 上找到完整的源代码: https://github.com/PacktPublishing/Spring-5.0-By-Example/tree/master/Chapter03/cms-mongodb/src /main/java/springfive/cms/domain/repository.

现在,我们需要更改服务层。让我们这样做。

Fixing the service layer

我们需要更改 service 层以采用新的反应式编程风格。我们更改了存储库层,因此现在我们需要修复由于此更改导致的编译问题结果。应用程序需要是反应式的。应用程序的任何点都可以被阻塞,因为我们使用的是事件循环模型。如果我们不这样做,应用程序将被阻止。

Changing the CategoryService

现在,我们将修复 CategoryService 类。我们将更改几个方法的 return 类型。之前,我们可以返回模型类,但现在我们需要更改为返回 MonoFlux,类似于我们所做的在存储库层。

新的 CategoryService 应该类似于以下代码片段中显示的实现:

package springfive.cms.domain.service;

import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import springfive.cms.domain.models.Category;
import springfive.cms.domain.repository.CategoryRepository;
import springfive.cms.domain.vo.CategoryRequest;

@Service
public class CategoryService {

  private final CategoryRepository categoryRepository;

  public CategoryService(CategoryRepository categoryRepository) {
    this.categoryRepository = categoryRepository;
  }

  public Mono<Category> update(String id,CategoryRequest category){
    return this.categoryRepository.findById(id).flatMap(categoryDatabase -> {
      categoryDatabase.setName(category.getName());
      return this.categoryRepository.save(categoryDatabase);
    });
  }

  public Mono<Category> create(CategoryRequest request){
    Category category = new Category();
    category.setName(request.getName());
    return this.categoryRepository.save(category);
  }

  public void delete(String id){
    this.categoryRepository.deleteById(id);
  }

  public Flux<Category> findAll(){
    return this.categoryRepository.findAll();
  }

  public Mono<Category> findOne(String id){
    return this.categoryRepository.findById(id);
  }

}

正如我们所见,方法中的返回类型发生了变化。

这里重要的是我们需要遵循反应性原则。当方法只返回一个实例时,我们需要使用Mono 。当方法返回一个或多个实例时,我们应该使用 Flux 。这是必须遵循的,因为开发人员和 Spring 容器可以正确解释代码。

 

update()方法 有一个有趣的调用: flatMap()。项目反应器允许我们使用一种 DSL 来编写调用。它非常有趣,也非常有用。它可以帮助开发人员创建比以前更容易理解的代码。 flatMap() 方法 通常用于转换Mono通量。在这种情况下,我们需要在从数据库中检索到的类别上设置新的类别名称。 

Changing the REST layer

我们还将对 REST 层进行一些修复。我们更改了 service 层,它在我们的资源类中引起了一些编译问题。

我们需要添加新的依赖项, spring-web-reactive。这支持响应式非阻塞引擎的 @Controller@RestController 注释。 Spring MVC 不支持响应式扩展,并且该模块使开发人员能够像以前一样使用响应式范式。

spring-web-reactive 将改变 Spring MVC 基础上的许多合约,例如 HandlerMappingHandlerAdapter,在这些组件上启用响应式基础。 

下图可以帮助我们更好地理解 Spring HTTP 层:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

正如我们所见,@Controller@RequestMapping 可以用于 Spring MVC 传统应用程序中的不同方法,或者通过使用Spring web 反应模块。

在开始更改 REST 层之前,我们需要删除项目中的 Spring Fox 依赖项和注释。目前,Spring Fox 尚不支持响应式应用程序。

 

要删除的依赖项是:

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger2</artifactId>
  <version>2.7.0</version>
</dependency>

<dependency>
  <groupId>io.springfox</groupId>
  <artifactId>springfox-swagger-ui</artifactId>
  <version>2.7.0</version>
</dependency>

之后,我们需要从 Swagger 包中移除注解,例如 @Api and @ApiOperation

现在,让我们调整我们的 REST 层。

Adding the Spring WebFlux dependency

在我们开始更改 REST 层之前,我们需要将新的依赖项添加到我们的 pom.xml

首先,我们将删除 Spring MVC 的传统依赖项。为此,我们需要删除以下依赖项:

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

我们不再需要这种依赖。我们的应用程序现在将是响应式的。然后,我们需要添加以下代码片段中描述的新依赖项:

<dependency>
  <groupId>io.netty</groupId>
  <artifactId>netty-transport-native-epoll</artifactId>
</dependency>

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

 

 

 

 

spring-boot-starter-webflux 是依赖的一种语法糖。它具有 spring-boot-starter-reactor-netty 依赖项,即 Reactor Netty,嵌入在响应式 HTTP 服务器中。

太棒了,我们的项目已经准备好转换 REST 层了。让我们将我们的应用程序转换为一个完全反应式的应用程序。

Changing the CategoryResource

我们将更改 CategoryResource 类。这个想法很简单。我们将使用 Mono< 将模型类参数化的 ResponseEntity 转换为 ResponseEntity /code> 或 Flux

CategoryResource 的新版本应该是这样的:

package springfive.cms.domain.resources;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import springfive.cms.domain.models.Category;
import springfive.cms.domain.service.CategoryService;
import springfive.cms.domain.vo.CategoryRequest;

@RestController
@RequestMapping("/api/category")
public class CategoryResource {

  private final CategoryService categoryService;

  public CategoryResource(CategoryService categoryService) {
    this.categoryService = categoryService;
  }

  @GetMapping(value = "/{id}")
  public ResponseEntity<Mono<Category>> findOne(@PathVariable("id") String id){
    return ResponseEntity.ok(this.categoryService.findOne(id));
  }

  @GetMapping
  public ResponseEntity<Flux<Category>> findAll(){
    return ResponseEntity.ok(this.categoryService.findAll());
  }

  @PostMapping
  public ResponseEntity<Mono<Category>> newCategory(@RequestBody CategoryRequest category){
    return new ResponseEntity<>(this.categoryService.create(category), HttpStatus.CREATED);
  }

  @DeleteMapping("/{id}")
  @ResponseStatus(HttpStatus.NO_CONTENT)
  public void removeCategory(@PathVariable("id") String id){
    this.categoryService.delete(id);
  }

  @PutMapping("/{id}")
  public ResponseEntity<Mono<Category>> updateCategory(@PathVariable("id") String id,CategoryRequest category){
    return new ResponseEntity<>(this.categoryService.update(id,category), HttpStatus.OK);
  }

}

该代码与我们之前所做的非常相似。我们在方法参数中使用了  @RequestBody注解;否则,JSON 转换器将无法工作。

这里的另一个重要特征是 return 方法。 它返回 Mono Flux,它们是 ResponseEntity 的参数化类型。

我们可以使用命令行来测试响应式实现。它将在 MongoDB 上持久化 Category 对象。在终端上键入以下命令:

curl -H "Content-Type: application/json" -X POST -d '{"name":"reactive"}' http://localhost:8080/api/category

 

 

 

 

 

 

然后,我们可以使用以下命令来检查数据库。使用浏览器,转到 http://localhost:8080/api/category。应显示以下结果:

读书笔记《developing-java-applications-with-spring-and-spring-boot-ebook》用Spring数据持久化和反应式时尚

太棒了,我们的反应式实现按预期工作。做得好!!!

Summary


在本章中,我们学习了很多 Spring 概念。我们向您介绍了 Spring Data 项目,它帮助开发人员创建我们以前从未见过的数据访问层。我们看到了使用这个项目创建存储库是多么容易。

此外,我们还展示了一些相对较新的项目,例如 Spring WebFlux,它允许开发人员创建现代 Web 应用程序,在项目中应用 Reactive Streams 基础和反应式编程风格。 

我们已经完成了我们的 CMS 应用程序。该应用程序具有生产就绪应用程序的特征,例如数据库连接和经过精心设计的单一职责的服务。此外,我们还引入了 docker-maven-plugin,它提供了一种使用 pom.xml 配置创建图像的合理方法.

在下一章中,我们将使用基于消息驱动应用程序的 Reactive Manifesto 创建一个新应用程序。在那里见。