首页 > 解决方案 > 在分页期间从 springframework.data.repository.reactive.ReactiveCrudRepository 返回 Mono 时出现“不满足的依赖关系”异常

问题描述

上下文:我成功创建了一个带有“org.springframework.data.domain.Pageable”参数的方法,旨在返回一个 Flux。当涉及 Pageable 时,我只找到了指导返回 Flux 而不是 Mono 的文章。到目前为止,一切都很好。

个人知识/假设:如果它只是一个页面,返回一个 Flux 是没有意义的。在其他世界中,它不是返回多个结果的流。好吧,如果我可以从 100 页中要求从第 2 页到第 10 页,我会看到一些关于禁止使用 Mono 的观点。据我了解,retrieveAllPaged bellow 中的真实事件是 0 事件或 1(从不超过 1)。

这是使用通量返回的方法:

public interface ModelRepository extends ReactiveCrudRepository<Extrato, String> {
    @Query("{ id: { $exists: true }}")
    Flux<Extrato> retrieveAllFluxPaged(final Pageable page);
}

暂定:因为它永远不会返回超过 1 我试过:

public interface ExtratoRepository extends ReactiveCrudRepository<Extrato, String> {

    @Query("{ id: { $exists: true }}")
    Mono<Extrato> retrieveAllMonoPaged(final Pageable page);
}

我得到了一个例外,它似乎表明 Mono 是不可接受的:

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testDataLoader' defined in file .... Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: public abstract reactor.core.publisher.Mono 

我的问题和直截了当的问题:分页时真的不可能返回 Mono 吗?如果是这样,相关评论将是为什么如果我从前端第二次调用它将是一个全新的搜索?我的意思是,例如,据我所知,从 RxJs 调用两次永远不会导致第二次调用重用第一次调用创建的通量。

import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map } from 'rxjs/operators';

@Injectable()
export class AppService {

  constructor(private http: HttpClient) {}

  getPage_1_and_2() {

    this.http
      .get<any[]>('http://localhost:8080/extrato/paged?page=1&size=1')
      .pipe(map(data => data));

    this.http
      .get<any[]>('http://localhost:8080/extrato/paged?page=2&size=1')
      .pipe(map(data => data));

  }

完整的日志

2020-03-25 15:25:16.803  WARN 11624 --- [           main] onfigReactiveWebServerApplicationContext : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testDataLoader' defined in file [C:\WSs\webflux\demo\target\classes\com\noblockingcase\demo\configuration\TestDataLoader.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'extratoRepository': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: public abstract reactor.core.publisher.Mono com.noblockingcase.demo.repository.ExtratoRepository.retrieveAllExtratosPaged(org.springframework.data.domain.Pageable)
2020-03-25 15:25:19.581  INFO 11624 --- [           main] ConditionEvaluationReportLoggingListener : 

Error starting ApplicationContext. To display the conditions report re-run your application with 'debug' enabled.
2020-03-25 15:25:19.587 ERROR 11624 --- [           main] o.s.boot.SpringApplication               : Application run failed

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'testDataLoader' defined in file [C:\WSs\webflux\demo\target\classes\com\noblockingcase\demo\configuration\TestDataLoader.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'extratoRepository': Invocation of init method failed; nested exception is java.lang.IllegalStateException: Method has to use a either multi-item reactive wrapper return type or a wrapped Page/Slice type. Offending method: public abstract reactor.core.publisher.Mono com.noblockingcase.demo.repository.ExtratoRepository.retrieveAllExtratosPaged(org.springframework.data.domain.Pageable)
    at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:798) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:228) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1358) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1204) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:557) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:517) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:323) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:321) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:879) ~[spring-beans-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:878) ~[spring-context-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.2.4.RELEASE.jar:5.2.4.RELEASE]
    at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.refresh(ReactiveWebServerApplicationContext.java:66) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:747) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:315) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1226) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1215) ~[spring-boot-2.2.5.RELEASE.jar:2.2.5.RELEASE]
    at com.noblockingcase.demo.DemoApplication.main(DemoApplication.java:12) ~[classes/:na]

标签: rxjsspring-dataspring-webfluxspring-mongodbspring-reactive

解决方案


编辑:澄清交互。

当您请求大小为 10 的“页面”时,在服务器上,您将从 repo 中获得一个 Flux,它将发出 10 个项目然后完成。因为 Angular 期望 1 个响应,所以 Spring 会将 Controller(非阻塞)中的所有 10 个元素收集到一个集合中,并在 Flux 完成时将它们发送出去。客户端 Observable 将发出 1 个值(一个数组),而不是 10 个离散项。您会在 Angular 有效负载中注意到这一点:

this.http
      .get<any[]>

因此,Flux 很好,因为 Spring 可以使用内容协商来做正确的事情。

如果您真的想使用 Mono,您可能需要 .collectList()。这会将您的 Flux 更改为列表的 Mono。

Mono<List<String>> listMono = Flux.just("foo", "bar", "baz").collectList();

如果你知道你的 Flux 真的只有 1 个值,你可以调用 .single() 。请注意,如果您产生 0 或 > 1 个元素,它将出错。

Mono<String> foo = Flux.just("foo").single();

推荐阅读