在一个管道中链接两个可为空的 Optional
Posted
技术标签:
【中文标题】在一个管道中链接两个可为空的 Optional【英文标题】:Chaining two nullable Optional in one pipeline 【发布时间】:2021-10-19 01:04:25 【问题描述】:我有 2 个实体 EntityOne
和 EntityTwo
映射到数据库中的 2 个表。
EntityTwo
中的所有列也存在于EntityOne
中,因为EntityTwo
是一个子系统的读取表。
现在,下面是一些解释场景的代码, 我正在使用 spring data repo 和 java Optional,带有 java-8
void doMagic(EntityOne entitytOne)
//Only if EntityOne present in Database, go delete entity Two.
repoOne
.findById(entitytOne.getPrimaryKey()); //returns Optional<EntityOne>
.ifPresent(this::deleteAssociatedEntityTwo);
void deleteAssociatedEntityTwo(EntityOne entityOne)
// Only If able to find an EntityTwo associated with EntityOne in Database, then delete it
fetchEntityTwo(entityOne) //returns Optional<EntityTwo>
.ifPresent(repoTwo::delete);
Absent EntityOne::createEntityTwoPrimaryKey
是 EntityOne
类中的一个方法,它创建一个新的 EntityTwoPrimaryKey 对象并填充所有值。
private Optional<EntityTwo> fetchEntityTwo(EntityOne entityOne)
// Only if passed entityOne is not null, query the Db and return Optional<EntityTwo>
return Optional
.ofNullable(entityOne)
.map(EntityOne::createEntityTwoPrimaryKey)
.map(repoTwo::findById) //returns Optional<EntityTwo>
.orElse(Optional.empty());
现在,如果您在所有上述 3 种方法中都看到了可选检查,以确保只有在我们拥有所有数据时才会执行操作。 我想在一个可选管道中编写所有代码。
如果我像下面这样写,则没有编译错误。但是当repoOne.findById
在数据库中找不到任何数据时会发生什么?
我只在repoTwo::findById
之后检查ifPresent()
repoOne.findById(entityOne.getEntityOnePrimaryKey())
.map(entityOne::createEntityTwoPrimaryKey)
.flatMap(repoTwo::findById) //returns Optional<EntityTwo>
.ifPresent(repoTwo::delete);
【问题讨论】:
【参考方案1】:让我们首先验证结果链是否正确,第一步是将方法分解为Optional
方法调用的单个链。
void doMagic(EntityOne entityOne)
repoOne.findById(entityOne.getPrimaryKey())
.ifPresent(eOne -> Optional
.ofNullable(eOne)
.map(entityOne::createEntityTwoPrimaryKey)
.flatMap(repoTwo::findById)
.ifPresent(repoTwo::delete));
现在我们可以将entity -> Optional.ofNullable(entity)
和ifPresent
折叠成一个扁平结构:
void doMagic(EntityOne entityOne)
repoOne.findById(entityOne.getPrimaryKey())
.map(entityOne::createEntityTwoPrimaryKey)
.flatMap(repoTwo::findById)
.ifPresent(repoTwo::delete);
到目前为止一切都很好,但是,还有一件事。但是有一件危险的事情,请注意这一行:
.map(entityOne::createEntityTwoPrimaryKey)
怎么了?此方法引用不调用 lambda 表达式捕获的实例的 createEntityTwoPrimaryKey
方法,而是传递给方法本身的方法!相当于:
.map(e -> entityOne.createEntityTwoPrimaryKey(e))
哪一个是不正确的,因为entityOne
可以是null
。你想使用:
// both are equivalent
.map(e -> e.createEntityTwoPrimaryKey(e))
.map(EntityOne::createEntityTwoPrimaryKey)
所以最终的链看起来像:
void doMagic(EntityOne entityOne)
repoOne.findById(entityOne.getPrimaryKey())
.map(EntityOne::createEntityTwoPrimaryKey)
.flatMap(repoTwo::findById)
.ifPresent(repoTwo::delete);
现在转换真的是正确的,那么我们回到问题上来:
如果我像下面这样写,则没有编译错误。但是当
repoOne.findById
在数据库中找不到任何数据时会发生什么?我只在repoTwo::findById
之后检查ifPresent()
。
考虑以下场景:
repoOne.findById
返回Optional.empty()
- 没有后续的map
和flatMap
被调用,因此没有创建任何内容。也没有调用ifPresent
,因此没有任何内容被删除。
repoOne.findById
返回一个非空的 Optional
但 map
会 - 同样,从 flatMap
到结尾的所有内容都不会被调用,因此不会创建或删除任何内容。
flatMap
进行的所有操作都返回一个非空的Optional
,但flatMap
确实如此——这里的事情开始变得有趣,因为createEntityTwoPrimaryKey
是它所做的任何事情,但ifPresent
中的删除不是。但是,我假设如果创建了某些东西,它也很可能会被找到,所以这种情况确实是一种极端情况。
最终结果取决于delete
方法调用,然而,它是void
返回类型。只要不抛出RuntimeException
,就是安全的。
总结,我发现Optional
链是安全的。您可能希望使用 @Transactional
注释该方法以回滚可能的 RuntimeException
。
【讨论】:
嗨,关于ifPresent(entityTwo::delete)
,这是一个错字。我更正为ifPresent(repoTwo::delete)
,但是您的其他假设是错误的,我给它的代码是正确的...createEntityTwoPrimaryKey
在“EntityOne”中。如果您查看有关此方法的评论,它会解释.. 它的作用
createEntityTwoPrimaryKey
方法是static
吗?
哦,为了清楚起见,我更新了,从EntityOne::createEntityTwoPrimaryKey
到entityOne::createEntityTwoPrimaryKey
的情况......所以现在它不是静态的,而是来自 entityOne 的状态......但是这对我询问的关于可选管道的问题有何影响?
您的答案看起来与我在问题中提到的完全一样。如果您阅读了我问题的最后一行,那就是主要问题。
entityOne::createEntityTwoPrimaryKey
和 EntityOne::createEntityTwoPrimaryKey
之间存在巨大差异。请,下次发布完全相同的代码。每次更改都很难修改我的答案...以上是关于在一个管道中链接两个可为空的 Optional的主要内容,如果未能解决你的问题,请参考以下文章