DCKing
DCKing

Reputation: 4273

Getting a JPA Criteria Query to work with an inherited list

Say I have an entity

@Entity
public class Test {
   @ManyToMany
   @JoinTable(..etc..)
   private List<Subject> subjects; // School subjects this test is associated with
   ....

And an entity

@Entity
public class Exam extends Test {
   // Inherits subjects from test
   // Does some things specific to exams
   ...

And I want to write a criteria query (with metamodels) that gives me only the Exams associated with a certain Subject. My question is: how do I write this query?

What I've tried is the following:

If I write

    CriteriaBuilder cb = em.getCriteriaBuilder();  // em is the EntityManager
    CriteriaQuery<Exam> cq = cb.createQuery(Exam.class);
    Root<Exam> root = cq.from(Exam.class);

    cq.where(cb.isMember(subject, root.get(Exam_.subjects)));

    return em.createQuery(cq);

the compiler won't compile it, saying error: no suitable method found for get(ListAttribute<Test,Subject>). Intuitively it feels as if this should be the solution, but inheritance won't go far enough. It won't work either if I omit the metamodel reference in the query and replace it with root.get("subjects").

If I write

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Exam> cq = cb.createQuery(Exam.class);
Root<Test> root = cq.from(Test.class);

cq.where(cb.isMember(subject, root.get(Exam_.subjects)));

return em.createQuery(cq);

This feels wrong, but it does compile. However, upon actually executing the code I am presented with an exception: IllegalStateException: No explicit selection and an implicit one could not be determined which I interpret as a consequence of juggling around the types for the Root. Trying root.get(Test_.subjects) yields the same result.

I use Hibernate as my JPA implementation, but I try to stick to JPA Criteria Queries.

Upvotes: 0

Views: 2905

Answers (2)

LagSeeing
LagSeeing

Reputation: 73

I finally found it is just a bug and will be fix in the jakartaee next version.

https://github.com/jakartaee/persistence/pull/173

the bug of root.get method.

So you can use like this temporarily

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Exam> cq = cb.createQuery(Exam.class);
Root<Exam> root = cq.from(Exam.class);

cq.where(cb.isMember(subject, root.get((PluralAttribute)Exam_.subjects)));

return em.createQuery(cq);

Upvotes: 0

JB Nizet
JB Nizet

Reputation: 691625

In JPQL (which I really advise you to use when you don't need a dynamically generated query), you would write it like this:

select e from Exam e
inner join e.students student
where student.id = :studentId

If you really want to use the Criteria API to write this query, do the same thing: create a join and check that the ID of the joined entity is equal to the given student ID. It should look like this:

Join<Exam, Student> studentJoin = root.join(Exam_.students);
cq.where(qb.equal(studentJoin.get(Student_.id), student.getId());

Upvotes: 0

Related Questions