在一个管道中链接两个可为空的 Optional

Posted

技术标签:

【中文标题】在一个管道中链接两个可为空的 Optional【英文标题】:Chaining two nullable Optional in one pipeline 【发布时间】:2021-10-19 01:04:25 【问题描述】:

我有 2 个实体 EntityOneEntityTwo 映射到数据库中的 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::createEntityTwoPrimaryKeyEntityOne 类中的一个方法,它创建一个新的 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 -&gt; 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() - 没有后续的mapflatMap 被调用,因此没有创建任何内容。也没有调用ifPresent,因此没有任何内容被删除。

repoOne.findById 返回一个非空的 Optionalmap 会 - 同样,从 flatMap 到结尾的所有内容都不会被调用,因此不会创建或删除任何内容。

flatMap 进行的所有操作都返回一个非空的Optional,但flatMap 确实如此——这里的事情开始变得有趣,因为createEntityTwoPrimaryKey 是它所做的任何事情,但ifPresent 中的删除不是。但是,我假设如果创建了某些东西,它也很可能会被找到,所以这种情况确实是一种极端情况。

最终结果取决于delete 方法调用,然而,它是void 返回类型。只要不抛出RuntimeException,就是安全的。

总结,我发现Optional 链是安全的。您可能希望使用 @Transactional 注释该方法以回滚可能的 RuntimeException

【讨论】:

嗨,关于ifPresent(entityTwo::delete),这是一个错字。我更正为ifPresent(repoTwo::delete),但是您的其他假设是错误的,我给它的代码是正确的...createEntityTwoPrimaryKey 在“EntityOne”中。如果您查看有关此方法的评论,它会解释.. 它的作用 createEntityTwoPrimaryKey方法是static吗? 哦,为了清楚起见,我更新了,从EntityOne::createEntityTwoPrimaryKeyentityOne::createEntityTwoPrimaryKey 的情况......所以现在它不是静态的,而是来自 entityOne 的状态......但是这对我询问的关于可选管道的问题有何影响? 您的答案看起来与我在问题中提到的完全一样。如果您阅读了我问题的最后一行,那就是主要问题。 entityOne::createEntityTwoPrimaryKeyEntityOne::createEntityTwoPrimaryKey 之间存在巨大差异。请,下次发布完全相同的代码。每次更改都很难修改我的答案...

以上是关于在一个管道中链接两个可为空的 Optional的主要内容,如果未能解决你的问题,请参考以下文章

多个可为空的 Guid,其中只有一个可以有值

可为空的对象必须具有一个值 是啥原因啊 求解 谢谢

有没有更优雅的方法来添加可为空的整数?

如何使用可为空的列连接 MySQL 表?

将可为空的 int 映射到可为空的 int + automapper

如果它为空或为空,如何从序列化中忽略可为空的属性?