仅当 Mono 为空时如何执行操作,如果不为空则抛出错误
Posted
技术标签:
【中文标题】仅当 Mono 为空时如何执行操作,如果不为空则抛出错误【英文标题】:How to perform an action only if the Mono is empty and throw an error if not empty 【发布时间】:2020-01-27 08:13:01 【问题描述】:我正在尝试将项目转换为使用 Spring WebFlux,但在使一些基本业务逻辑正常工作时遇到了问题。我有一个负责检索/保存记录的存储库层和一个负责应用程序业务规则的服务层。我想要做的(在服务层)是检查给定用户名的用户是否已经存在。如果是这样,我想回复一个错误。如果没有,我想允许插入发生。
我在存储库层调用了一个方法,该方法将通过用户名查找用户,如果未找到,它将返回一个空的 Mono。这按预期工作;但是,我尝试了 flatMap 和 (defaultIfEmpty 和 swithIfEmpty) 的各种组合,但无法编译/构建。
public Mono<User> insertUser(User user)
return userRepository.findByUsername(user.username())
.flatMap(__ -> Mono.error(new DuplicateResourceException("User already exists with username [" + user.username() + "]")))
.switchIfEmpty(userRepository.insertUser(user));
我得到的错误是 Mono<Object> cannot be converted to Mono<User>
,所以 swithIfEmpty
似乎没有反映适当的类型,并且转换似乎也不起作用。
【问题讨论】:
你用的是什么编译器? 您使用什么来将数据保存在存储库中? 我正在使用 Java 11 JDK 构建。持久化是在 Postgresql 中完成的。但是,我现在只是想让一个单元测试适用于这个逻辑,所以存储库层被模拟了。 【参考方案1】:经过额外的测试,并考虑到其他开发人员的反馈,我找到了以下解决方案:
public Mono<User> insertUser(User user)
return userRepository.findByUsername(user.username())
.flatMap(__ -> Mono.error(new DuplicateResourceException("User already exists with username [" + user.username() + "]")))
.switchIfEmpty(Mono.defer(() -> userRepository.insertUser(user)))
.cast(User.class);
正如 Thomas 所说,编译器感到困惑。我的假设是因为flatMap
返回一个带有错误的 Mono,而 switchIfEmpty
返回一个带有用户的 Mono,因此它恢复为带有 Object 的 Mono(因此额外的 .cast
运算符让它编译) .
另一个添加是在switchMap
中添加Mono.defer
。否则,switchIfEmpty
总是在触发。
我仍然对其他建议/替代方案持开放态度(因为这似乎是一种相当普遍的需求/模式)。
【讨论】:
嗨@cgaskill!为什么要使用 Mono.defer 方法?【参考方案2】:您收到此编译器错误的原因如下。
flatmap
获取已完成的Mono
中的内容,并尝试将其转换为它可以推断的任何类型。 Mono.error
包含一个类型,该类型属于Object
。
一种方法可能是将您的逻辑移动到平面图中。
// This is just example code using strings instead of repos
public Mono<String> insertUser(String user)
return Mono.just(user)
// Here we map/convert instead based on logic
.flatMap(__ ->
if (__.isEmpty())
return Mono.error(new IllegalArgumentException("User already exists with username [" + user + "]"));
return Mono.just(user);
).switchIfEmpty(Mono.just(user));
switchIfEmpty
不适合做出合乎逻辑的决定,恕我直言。文档说明
如果此单声道已完成,则回退到备用单声道 没有数据
如果我们没有得到任何东西,这更像是对其他东西的后备,因此我们可以保持数据流继续进行。
你也可以
Mono.empty().doOnNext(o ->
throw new IllegalArgumentException("User already exists with username [" + o + "]");
).switchIfEmpty(Mono.just("hello")).subscribe(System.out::println);
【讨论】:
flatMap
将返回通过调用userRepository.findUserByUsername
找到的用户,它没有像字符串那样的isEmpty
方法。我的期望是,如果对userRepository.findUserByUsername
的调用简单地完成而不发出用户,则flatMap
中的代码甚至不会被调用,switchIfEmpty
(或defaultIfEmpty
)中的代码将被调用,这将允许我插入用户(因为它们不存在)。
ofc 你的用户没有isEmpty
方法,这不是重点。您可以改为空检查。如果完成但返回空,则不会进入平面地图。但是如果它完成并且有一个值,编译器怎么知道你想要返回什么,你现在告诉它返回Mono<Object>
。但是编译器怎么知道你的意图呢? flatMap 需要能够推断返回类型,并且在您的代码中,您告诉它推断 Mono
但是,我的解决方案是在数据库中的字段上放置一个唯一约束,然后您不需要在应用程序中执行所有这些逻辑,您总是尝试保存,如果保存失败,将错误返回给客户端。
如果您在数据库中执行实际删除,这将起作用。我们的过程是永不删除(我们不喜欢丢失数据),因此我们执行逻辑删除(我们停用记录)。所以我们不能有一个唯一的约束,因为我们希望允许另一个用户使用相同的用户名(只要它是非活动的)。所以只有 1 条具有该用户名的活动记录。【参考方案3】:
我正在使用具有类型参数E
的抽象类,所以我不能使用.cast(E.class)
。我们的解决方案是
private Mono<E> checkIfStatementExists(E statement)
return this.statementService.getByStatementRequestId(statement.getStatementRequestId())
.flatMap(sr -> Mono.<E>error(new ValidationException("Statement already exists for this request!")))
.switchIfEmpty(Mono.just(statement));
我想我需要在下周与我的同事讨论这件事。
编辑。我们和同事讨论过,更新的代码在上面。
【讨论】:
【参考方案4】:遇到同样的问题,结果在下面做,
请注意:这不需要类型转换并且适用于两种情况,即
如果元素已存在于数据库中,则抛出错误。
插入元素,否则返回 Mono。
public Mono<UserDTO> insertUser(User user)
return this.userRepository.findByUsername(user.getUsername())
.flatMap(foundUser-> null != foundUser ? Mono
.error(new UserAlreadyExistsException(ExceptionsConstants.USER_ALREADY_EXISTS_EXCEPTION))
: Mono.just(user))
.switchIfEmpty(this.userRepository.save(user))
.map(userCreated -> copyUtils.createUserDTO(userCreated));
【讨论】:
以上是关于仅当 Mono 为空时如何执行操作,如果不为空则抛出错误的主要内容,如果未能解决你的问题,请参考以下文章