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

Chaining two nullable Optional in one pipeline

我有 2 个实体 EntityOneEntityTwo 映射到数据库中的 2 个 table。 EntityTwo 中的所有列也出现在 EntityOne 中,因为 EntityTwo 是一个子系统的读取 table。

现在,下面是一些代码来解释一个场景, 我正在使用 spring 数据回购和 java 可选,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 class 中的一个方法,它创建一个新的 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);

让我们首先验证生成的链是否正确,第一步是将方法分解为 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))

是正确的,因为 NPE 容易出错,因为 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);
}

现在转换确实正确了,那我们回到问题:

If I write like below, There are no compilation errors. But what will happen when repoOne.findById can't find any data in the database? I'm only checking ifPresent() after repoTwo::findById.

考虑以下场景:

  • repoOne.findById returns Optional.empty() - 没有后续的 mapflatMap 被调用,因此没有创建任何东西。也没有调用 ifPresent,因此没有删除任何内容。

  • repoOne.findById returns 是一个非空的 Optionalmap 是 - 同样,从 flatMap 到最后的所有内容都是未调用,因此不会创建或删除任何内容。

  • 一切都在进行 flatMap return 是一个非空的 Optional,但是 flatMap 是 - 这里的事情开始变得有趣,因为 createEntityTwoPrimaryKey 是它所做的一切,但 ifPresent 中的删除不是。但是,我假设如果创建了某些东西,它也很可能会被发现,所以这种情况确实是一种边缘情况。

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

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