Search by nested property of collection field with criteria api
Asked Answered
B

4

6

I'm trying to find all entities that have some nested elements and nested elemens have collections of elements, and I need to find it by property of those collections.

It would be something like this

class A{
    private B b;
}

class B{
   private Collection<C> cCol;
}

class C{
   private String name;
}

So I want to get all A elements that have B elements that have a C which name matches given parameter.

Not sure how to do it with JPA Critieria API. I know there is in predicate, or MEMEBER OF in JPQL but I need to search by property of element in collection, not a collection member.

Tried things like root.get(a.b.c.name) and also with root.fetch(a.b) or root.fetch(b.c) but always ended up with some exceptions about illegal api usage

Bruges answered 7/11, 2016 at 9:15 Comment(0)
N
13

I want to get all A elements that have B elements that have a C which name matches given parameter.

When trying to navigate the criteria API I find it immensely helpful to write the JPQL query first. Here it is:

SELECT a
FROM A a
WHERE EXISTS(
    SELECT c FROM a.b b JOIN b.cCol c WHERE c.name = 'condition'
)

Now the criteria API becomes clearer (if that is possible at all):

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<A> aQuery = cb.createQuery(A.class);
Root<A> a = aQuery.from(A.class);

Subquery<C> cSubquery = aQuery.subquery(C.class);
Root<A> aSubroot = cSubquery.correlate(a);
Join<A, B> b = aSubroot.join("b");  // "b" is the name of the property of A that points to B
Join<B, C> c = b.join("cCol"); // "cCol" is the name of the property of C that holds the related C objects

cSubquery.select(c);
cSubquery.where(cb.equal(c.get("name"), "XXXXXXX"));

aQuery.where(cb.exists(cSubquery));

TypedQuery<A> aTypedQuery = em.createQuery(aQuery);
aTypedQuery.getResultList();

The names of the Java variables are the same as in the JPQL, e.g. Join<A, B> b corresponds to the JPQL FROM a.b b.

Nuzzle answered 7/11, 2016 at 13:28 Comment(2)
aQuery.distinct(true).where(cb.equal(root.get("b").get("cCol").get("name"), searchString)) should also give the same results without the need for a subquery - the equivalent JPQL query would be SELECT DISTINCT a from A a JOIN a.b b JOIN b.cCol c WHERE c.name = :searchStringWatchword
The reason I prefer subqueries over DISTINCT JOIN is performance. Although I am not an expert in this, here are some resources supporting it: (1), (2), (3), (4). The actual result and performance gain depends on the exact use case of course.Nuzzle
U
5

The below should work

root.get("a").get("b").get("name")

See

How to create specification using JpaSpecificationExecutor by combining tables?

Urina answered 7/11, 2016 at 9:26 Comment(4)
From the used field names it should be root.get("b").get("cCol").get("name") (assuming root is Root<A>).Watchword
Unfortunatelly this fails with IllegalStateException: Illegal attempt to dereference path source [null.b.cCol] of basic typeBruges
@Bruges - you should probably post more code on how you try to create the queryWatchword
I've been trying to write a test for this without success because the get("cCol") is actually a collection.Urina
M
0

I was successful by using

root.**join**("a").get("b").get("name"):

because it's a collection.

Mier answered 21/5, 2021 at 16:39 Comment(0)
T
0

I did it by using joins like this:

builder = entityManager.getCriteriaBuilder();
CriteriaQuery<A> query = builder.createQuery(A.class);
Root<A> root = query.from(A.class);
root.join("b").join("c").get("name");
Toga answered 3/8, 2021 at 13:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.