简体   繁体   中英

JPA criteria builder only return entity if join column does not contain a certain value

I have a two classes:

@Entity
@Table(name = "foo")
data class Foo {
  @Id
  val id: UUID
  // a lot more values

  @ElementCollection(fetch = FetchType.EAGER)
  @CollectionTable(name = "foo_bar", joinColumns = [JoinColumn(name = "foo_bar_id")])
  val bars: Set<FooBar>?
}

data class FooBar {
  val fooBarId: UUID
  val bar: Bar
  val validUntil: Instant
}

At the moment i have an extensive search function on the this entity using Predicate and CriteriaBuilder. I want to be able to include only Foo objects in our search that have a specific Bar value in their FooBar JoinColumn row and a function that only returns the FooBar Object if none of the joinColumn rows have that specific Bar value.

What i have up to now is:



    fun searchPaged(
        // numerous criteria values
        flipFilterOnBar: Boolean?,
        bar: Bar?,
        pageable: Pageable
    ): Page<Foo> {
  return findAll({ root, _, cb ->
    
    val predicates = mutableListOf<Predicate>(
                cb.or(
                   //some criteria
                )
            )

     //more criteria
     //
     //

      if (bar != null) {
                val joinColumn: Join<Foo, FooBar> = root.join<Foo?, FooBar?>("bars", JoinType.INNER)
                if (flipFilterOnBar == true) { //Nullable boolean
                    
                    // this part makes the query return a row for each join column and filters out only the filtered value (returning more rows than if this clauses was not added.
                    predicates.add(
                    cb.notEqual(joinColumn.get<Bar>("bar"), bar)
                    )
                } else {
                    predicates.add(
                        cb.equal(joinColumn.get<Bar>("bar"), bar)
                    )
                }
            }

            cb.and(*predicates.toTypedArray())
        }, pageable)

I would like to know how i can use the criteria builder here to only return the Bar Entity if none of the FooBar rows contains the specified Bar value. The way i have written it at the moment joins all Foo and FooBar rows together and only filters the combined row that has the specified Bar value, returning the other combined row which is not what i want.

For instance if i have a Foo with a collection of 3 FooBars and i want to filter out the all Foo's which have a Bar with the value "b". The Query retrieves this Foo 3 times (one for each Bar element) and than only filters the combined row which has the Foo with value "b" in it. Returning the other two rows. I would like the query to return none of those rows, since one of the rows contains the value "b".

try using a predicate similar to all foos that passed the previous predicates and whose ids is not in the collection of ids of foos with foobars whose bar value is "b".

        Subquery<UUID> subquery = criteriaQuery.subquery(UUID.class);
        Root<Foo> fooSubqueryRoot = subquery.from(Foo.class);
        Join<Foo, FooBar> fooBarSubqueryJoin = fooSubqueryRoot.join("bars");
        Join<FooBar, Bar> barSubqueryJoin = fooBarSubqueryJoin.join("bar");
        subquery.where(cb.equal(barSubqueryJoin.get("value"), "b"));
        //foos ids of foos with foobars whose bar value is "b"
        subquery.distinct(true).select(fooSubqueryRoot.get("id"));
        //exclude foos with foobars whose bar value is "b"
        predicates.add(root.get("id").in(subquery).not());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM