首页 > 数据库 >13-MongoDB 集成:如何在响应式应用中访问 NoSQL 数据库

13-MongoDB 集成:如何在响应式应用中访问 NoSQL 数据库

时间:2023-07-11 23:31:37浏览次数:34  
标签:13 NoSQL MongoDB class Reactive Spring Data public

上一讲开始,我们进入了响应式数据访问这一模块的学习,并且引出了 Spring 家族中专门用于实现数据访问的 Spring Data 框架及其响应式版本。我们知道 Spring Data 支持多种响应式 Repository 用来构建全栈响应式编程模型,而 MongoDB 就是其中具有代表性的一种数据存储库。今天,我就将结合案例来给出 Reactive MongoDB 的使用方式。

Spring Data MongoDB Reactive 技术栈

在介绍 Spring Data MongoDB Reactive 的使用方式之前,我们先来简要分析它的基本组成结构和所使用的技术栈。

显然,ReactiveMongoRepository 是开发人员所需要面对的第一个核心组件。

public interface ReactiveMongoRepository<T, ID> extends 
 ReactiveSortingRepository<T, ID>, ReactiveQueryByExampleExecutor<T> {
  
   <S extends T> Mono<S> insert(S entity);
   <S extends T> Flux<S> insert(Iterable<S> entities);
   <S extends T> Flux<S> insert(Publisher<S> entities);
   <S extends T> Flux<S> findAll(Example<S> example);
   <S extends T> Flux<S> findAll(Example<S> example, Sort sort);
 }

Spring Data MongoDB Reactive 模块只有一个 ReactiveMongoRepository 接口的实现类,即 SimpleReactiveMongoRepository 类。它为 ReactiveMongoRepository 的所有方法提供实现,并使用 ReactiveMongoOperations 接口处理针对 MongoDB 的数据访问操作。例如在 SimpleReactiveMongoRepository 类中有一个 findAllById 方法,如下所示。

@Override
     public Flux<T> findAllById(Publisher<ID> ids) {
  
         Assert.notNull(ids, "The given Publisher of Id's must not be null!");
         return Flux.from(ids).buffer().flatMap(this::findAllById);
     }

可以看到这个方法使用 buffer 操作符收集所有 ids,然后使用 findAllById(Iterable <ID> ids) 重载方法创建一个请求。

该方法反过来构建 Query 对象并调用 findAll(Query query) 方法,这时候触发 ReactiveMongoOperations 实例的 mongoOperations.find (query,...) 方法。

而 ReactiveMongoOperations 接口的实现类就是 ReactiveMongoTemplate 类,在这个模板工具类中,基于 MongoDB 提供的 Java 驱动程序完成对数据库的访问。我们在 ReactiveMongoTemplate 类所引用的包结构中可以看到这些驱动程序的客户端组件,如下所示。

import com.mongodb.client.model.UpdateOptions;
 import com.mongodb.client.result.UpdateResult;
 …
  
 import com.mongodb.reactivestreams.client.MongoClient;
 import com.mongodb.reactivestreams.client.MongoCollection;
 …

Spring Data 中的响应式 MongoDB 连接基于 MongoDB 响应式流 Java 驱动程序(mongo-java-driver-reactivestreams)构建。该驱动程序提供具有非阻塞背压的异步流处理。另一方面,响应式驱动程序构建在 MongoDB 异步 Java 驱动程序(mongo-java-driver-async)之上。这个异步驱动程序是低级别的,并且具有基于回调的 API,因此它不像更高级别的响应式流驱动程序那样易于使用。下图展示了 Spring Data 中整个响应式 MongoDB 的技术栈。Spring Data MongoDB Reactive 技术栈:

13-MongoDB 集成:如何在响应式应用中访问 NoSQL 数据库_mongodb

而下图展示了基于 Maven 依赖所找到的对应的组件库。Spring Data MongoDB Reactive 中的组件库:

13-MongoDB 集成:如何在响应式应用中访问 NoSQL 数据库_Data_02

应用 Reactive MongoDB

使用 Spring Data MongoDB Reactive 进行系统开发的具体过程,将从开发环境的准备、Repository 的创建、数据的初始化以及与 Service 层之间的集成等几个步骤介绍。

初始化 Reactive MongoDB 运行环境

添加依赖:

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

通过Maven查看spring-boot-starter-data-mongodb-reactive组件依赖图:

13-MongoDB 集成:如何在响应式应用中访问 NoSQL 数据库_响应式_03

可见 spring-boot-starter-data-mongodb-reactive 组件同时依赖 spring-data-mongodb、mongodb-driver-reactivestreams 以及 reactor-core 等组件。

为集成 Reactive MongoDB,在 Spring Boot 应用程序中,我们可以在它的启动类上添加 @EnableReactiveMongoRepositories 注解,包含该注解的 Spring Boot 启动类如下所示。

@SpringBootApplication
 @EnableReactiveMongoRepositories
 public class SpringReactiveMongodbApplication {
  
   public static void main(String[] args) {
       SpringApplication.run(SpringReactiveMongodbApplication
   .class, args);
   }
 }

默认一般无需在 Spring Boot 启动类中手工添加 @EnableReactiveMongoRepositories。因为当添加 spring-boot-starter-data-mongodb-reactive 组件到 classpath 时,MongoReactiveRepositoriesAutoConfiguration 配置类会自动创建与 MongoDB 交互的核心类。

MongoReactiveRepositoriesAutoConfiguration定义:

@Configuration
 @ConditionalOnClass({ MongoClient.class, ReactiveMongoRepository.class })
 @ConditionalOnMissingBean({ ReactiveMongoRepositoryFactoryBean.class,
       ReactiveMongoRepositoryConfigurationExtension.class })
 @ConditionalOnProperty(prefix = "spring.data.mongodb.reactive-repositories", name = "enabled", havingValue = "true", matchIfMissing = true)
 @Import(MongoReactiveRepositoriesAutoConfigureRegistrar.class)
 @AutoConfigureAfter(MongoReactiveDataAutoConfiguration.class)
 public class MongoReactiveRepositoriesAutoConfiguration {
  
 }

可见这里引入了 MongoReactiveRepositoriesAutoConfigureRegistrar 类,如果 MongoDB 和 Spring Data 已经在 classpath 上,Spring Boot 会通过 MongoReactiveRepositoriesAutoConfigureRegistrar 类自动帮我们完成配置。MongoReactiveRepositoriesAutoConfigureRegistrar 类的定义如下。

class MongoReactiveRepositoriesAutoConfigureRegistrar
       extends AbstractRepositoryConfigurationSourceSupport {
  
   @Override
   protected Class<? extends Annotation> getAnnotation() {
       return EnableReactiveMongoRepositories.class;
   }
  
   @Override
   protected Class<?> getConfiguration() {
       return EnableReactiveMongoRepositoriesConfiguration.class;
   }
  
   @Override
   protected RepositoryConfigurationExtension getRepositoryConfigurationExtension() {
       return new ReactiveMongoRepositoryConfigurationExtension();
   }
  
   @EnableReactiveMongoRepositories
   private static class EnableReactiveMongoRepositoriesConfiguration {
  
   }
 }

代码最后看到@EnableReactiveMongoRepositories

  • 若使用 Spring Boot默认配置,就无需在启动类添加 EnableReactiveMongoRepositories
  • 但若希望修改 MongoDB 配置行为,该注解就能派上用场

@EnableReactiveMongoRepositories使用方法

@Configuration
 @EnableReactiveMongoRepositories(basePackageClasses = 
   OrderRepository.class)
 public class MongoConfig extends AbstractReactiveMongoConfiguration {
  
     @Bean
     @Override
     public MongoClient reactiveMongoClient() {
         return MongoClients.create();
     }
  
     @Override
     protected String getDatabaseName() {
         return "order_test";
     }
     @Bean
 public ReactiveMongoTemplate mongoTemplate() throws Exception {
         return new ReactiveMongoTemplate(mongoClient(), getDatabaseName());
 }
 }

通过 @EnableReactiveMongoRepositories 指定“basePackageClasses”为OrderRepository,同时修改所要访问的数据库名为“order_test”,相当于对 Repository 类以及数据库的名称做了人为的指定,而在默认情况下系统会自动扫描 Repository 类,并默认使用领域实体的名称作为数据库名。

创建 Reactive MongoDB Repository

创建一个 Reactive MongoDB Repository。在这里,我们定义了一个领域实体 Account,并使用 @Document 和 @Id 等 MongoDB 相关的注解。Account 实体代码:

@Document
 public class Account {
   
     @Id
     private String id;
     private String accountCode;
     private String accountName;
 }

可通过上一讲介绍的三种方式中的的任何一种来创建 Reactive MongoDB Repository。我们可以定义继承自 ReactiveMongoRepository 接口的 AccountReactiveMongoRepository 接口,同时该接口还继承了 ReactiveQueryByExampleExecutor 接口。

AccountReactiveMongoRepository 接口定义的代码如下所示,可以看到我们完全基于 ReactiveMongoRepository 和 ReactiveQueryByExampleExecutor 接口的默认方法来实现业务功能。

@Repository
 public interface AccountReactiveMongoRepository
 extends ReactiveMongoRepository<Account, String>, 
   ReactiveQueryByExampleExecutor<Account> {
 }

使用 CommandLineRunner 初始化 MongoDB 数据

对于 MongoDB 等数据库而言,我们通常需要执行一些数据初始化操作。接下来我将介绍如何通过 Spring Boot 提供的 CommandLineRunner 实现。

在系统运行前执行一些前置操作,Spring Boot 提供 CommandLineRunner 接口:

public interface CommandLineRunner {
         void run(String... args) throws Exception;
 }

Spring Boot 应用程序在启动后,会遍历所有已定义的 CommandLineRunner 接口的实例并运行它们的 run() 方法。

在 MongoDB 客户端组件中存在一个 MongoOperations 工具类。相对于 Repository 接口而言,MongoOperations 提供了更多方法,也更接近于 MongoDB 原生态语言。基于 CommandLineRunner 和 MongoOperations,可对 MongoDB 进行数据初始化:

public class InitDatabase {
     @Bean
     CommandLineRunner init(MongoOperations operations) {
         return args -> {
            operations.dropCollection(Account.class);
  
            operations.insert(new Account("A_" + UUID.randomUUID().toString(),"account1", "jianxiang1"));
            operations.insert(new Account("A_" + UUID.randomUUID().toString(),"account2", "jianxiang"));
            operations.findAll(Account.class).forEach(
                    account -> {
                        System.out.println(account.getId()
                    );}
            );
         };
     }
 }

在这个例子中,我们先通过 MongoOperations 的 dropCollection() 方法清除整个 Account 数据库中的数据,然后往该数据库中添加了两条记录,最后我们通过 findAll() 方法执行查询操作,获取新插入的两条数据并打印在控制台上。

在 Service 层中调用 Reactive Repository

完成 AccountReactiveMongoRepository 并初始化数据之后,我们就可以创建 Service 层组件来调用 AccountReactiveMongoRepository。

创建了 AccountService 类作为 Service 层组件:

@Service
 public class AccountService{
 
     @Autowired
     private final AccountReactiveMongoRepository accountRepository;
     public Mono<Account> save(Account account) {
         return accountRepository.save(account);
     }
  
     public Mono<Account> findOne(String id) {
         return accountRepository
 .findById(id).log("findOneAccount");
     }
  
     public Flux<Account> findAll() {
         return accountRepository.findAll().log("findAllAccounts");
     }
  
     public Mono<Void> delete(String id) {
         return accountRepository
 .deleteById(id).log("deleteOneAccount");
     }
     public Flux<Account> getAccountsByAccountName(String accountName) {
       Account account = new Account();
       account.setAccountName(accountName);
  
       ExampleMatcher matcher = ExampleMatcher.matching()
           .withIgnoreCase()
           .withMatcher(accountName, GenericPropertyMatcher.of(StringMatcher.STARTING))
           .withIncludeNullValues();
  
       Example<Account> example = Example.of(account, matcher);
       Flux<Account> accounts = accountRepository.findAll(example).log("getAccountsByAccountName");
    return accounts; } }

AccountService 类中的 save()、findOne()、findAll() 和 delete() 方法都来自 ReactiveMongoRepository 接口,而最后的 findByAccountName() 方法则使用了 ReactiveQueryByExampleExecutor 接口所提供的 QueryByExample 机制。

QueryByExample 可以翻译成按示例查询,是一种用户友好的查询技术。它允许动态创建查询,并且不需要编写包含字段名称的查询方法。实际上,QueryByExample 不需要使用特定的数据库查询语言来编写查询语句。从组成结构上讲,QueryByExample 包括 Probe、ExampleMatcher 和 Example 这三个基本组件。其中 Probe 包含对应字段的实例对象;ExampleMatcher 携带有关如何匹配特定字段的详细信息,相当于匹配条件;而 Example 则由 Probe 和 ExampleMatcher 组成,用于构建具体的查询操作。

在上述示例代码中,我们首先构建了一个 ExampleMatcher 用于初始化匹配规则,然后通过传入一个 Account 对象实例和 ExampleMatcher 实例构建了一个 Example 对象,最后通过 ReactiveQueryByExampleExecutor 接口中的 findAll() 方法实现了 QueryByExample 机制。

同时,你也应该注意到,在 AccountService 的 findOne()、findAll()、delete() 以及 findByAccountName() 这四个方法的最后都调用了 log() 方法,该方法使用了 Reactor 框架中的日志操作符,我们在“08 | Reactor 操作符(下):如何多样化裁剪响应式流”中有详细介绍。

通过添加 log() 方法,在执行这些数据操作时就会获取 Reactor 框架中对数据的详细操作日志信息。在这个示例中,我们启动服务并执行这四个方法,会在控制台中看到对应的日志。其中一部分日志展示了服务启动时通过 CommandLineRunner 插入初始化数据到数据库的过程,另一部分则分别针对各个添加了 log() 方法的操作打印出数据流的执行效果。

Service 层通过 log() 方法添加日志是一种常见的开发技巧。

案例集成:构建基于 MongoDB 的数据访问层

在介绍完如何使用 Spring Data MongoDB Reactive 构建基于 MongoDB 的数据访问组件之后,让我们来到 ReactiveSpringCSS 案例中。针对案例中的数据访问场景,本讲所介绍的相关技术都可以直接进行应用。

事实上,在前面的介绍中,我们已经构建了 account-service 中的数据访问层,而其他两个服务中的数据访问层也类似,不再细说。

总结

MongoDB 是一款主流的 NoSQL 数据库,其提供了实现响应式流的驱动程序,因此非常适合作为响应式系统中的持久化数据库。而 Spring 家族中的 Spring Data MongoDB Reactive 组件则提供了以响应式流的方法访问 MongoDB 的高效开发模式,本讲结合案例对这一组件的使用方式进行了详细的讨论。

FAQ

使用 Spring Data MongoDB Reactive 时,针对查询操作可以使用哪些高效的实现方法?

在今天内容的基础上,下一讲我们将基于 Spring Data 框架中的 Spring Data Redis 组件来访问缓存数据库 Redis,并同样结合 ReactiveSpringCSS 案例完成对现有实现方式的重构。

标签:13,NoSQL,MongoDB,class,Reactive,Spring,Data,public
From: https://blog.51cto.com/JavaEdge/6693513

相关文章

  • ubuntu20.04安装mongodb步骤
    注:虚拟机无法运行mongodb5.0以上的版本1、wget-qO-https://www.mongodb.org/static/pgp/server-4.4.asc|apt-keyadd-导入并设置公钥2、echo"deb[arch=amd64,arm64]https://repo.mongodb.org/apt/ubuntufocal/mongodb-org/4.4multiverse"|tee/ect/apt/sources.list.......
  • 代码随想录算法训练营第二十九天| 1005.K次取反后最大化的数组和 134. 加油站 135. 分
      860.柠檬水找零 思路:遇到20,先给10和5,再给三个5代码:1boollemonadeChange(vector<int>&bills){2if(bills.size()==0)returntrue;34map<int,int>currentMoney;5for(inti=0;i<bills.size();i++)6{7if......
  • mongodb4集合操作
    Mongodb集合操作1.查看当前数据库下所有集合showcollectionsshowtables2.新建集合db.createCollection("product")3.新建集合并插入文档db.girl.insert({"name":"小花","age":"33","address":"chengdu"})4.查看文档内容db.girl.find()5.删除......
  • mongodb5文档操作1-插入数据
    mongodb插入文档数据1.insert插入单条数据info={ "name":"teacher01","num":01,"age":33,"sex":"man","phone":18782940194,"address":"成都龙泉"}db.teacher.insert......
  • mongodb5文档操作2-聚合查询数据
    Mongodb聚合函数查询1.match函数db.person.aggregate([{"$match":{"address":"龙泉"}}])2.group分组查询db.person.aggregate([{$group:{_id:"$address",count:{$sum:1}}}])3.按条件分组查询db.person.aggregate([{$match:{se......
  • mongodb5文档操作2-排序查询数据
    Mongodb查询排序操作1.通过1控制正序显示db.teacher.find().sort({"_id":1})2.通过-1控制倒序显示db.teacher.find().sort({"_id":-1})......
  • mongodb2用户操作
    mongodb用户操作一.给所有数据库添加访问认证1.编辑mongodb.conf添加参数auth=true权限vi/usr/local/mongodb/bin/mongodb.conf添加auth=true参数2.重启mongodb,未认证直接使用showdbs无法查看二.用户权限操作1.系统用户权限和角色介绍用户角色数据库用户角色:read、readWrite;数据......
  • mongodb3数据库操作
    Mongodb数据库操作一.默认数据库1.showdbs查看数据库admin库主要存储MongoDB的用户、角色等信息config库主要存储分片集群基础信息local库主要存储副本集的元数据test默认空数据库,无法查看2.amdin数据库syetem内容介绍system.version存储authSchema的版本信息system.users存储数据......
  • React18+Next.js13+TS,B端+C端完整业务+技术双闭环(20章)
    最新React技术栈,实战复杂低代码项目-仿问卷星第1章开期准备试看3节|20分钟介绍课程内容,学习建议和注意事项。演示课程项目,让学员有一个整体的认识。第2章【入门】什么是ReactReact引领了现代前端开发的变革8节|50分钟介绍React的历史、背景和每次版本更新。介绍R......
  • 出去面试人家狂问ZK分布式锁13个连环炮,你觉得你能抗住吗?
    前言这篇文章我们来剖析Zookeeper分布式锁的实现框架Curator的源码,看看Curator是如何实现Zookeeper分布式锁的,以及它提供的哪些其它的特性。Curator框架是封装对于zk操作的api,其中就包括了对分布式锁的实现,当然Curator框架也包括其它的功能,分布式锁只是Curator的一部分功能。一、ZK......