Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Polymorphic JPA query with CriteriaQuery API

Tags:

java

jpa

api

I have the following entity structure:

   +-----------+                +-------------+        
   |  User     | -------------> |    Role     |
   +-----------+                +-------------+
                                       ^
                                       |
                               +-------+---------+
                               |                 |
                        +-----------+      +-----------+           +------------+
                        |   Role1   |      |  Role2    |-------->  | SomeEntity |
                        +-----------+      +-----------+           +------------+

and I want to get the names of all User whose Role2 has a SomeEntity with a specific property value. I need to do this with the JPA criteria API.

What I did so far is:

CriteriaBuilder cb = ...
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<User> user = query.from(User.class);
SetJoin<User, Role> userRolesJoin = user.join(User_.roles);

// As you can see the userRolesJoin is of type Role and a Role doesn't have
// an property someEntity. So how to "cast" the userRolesJoin into an 
// SetJoin<User, Role2>.

How to do the polymorphic query here? Any suggestions?

Unfortunatly the JPA criteria API is not as intuitive as Hibernate's critera API.

like image 352
René Link Avatar asked Jun 20 '13 11:06

René Link


2 Answers

Solved it using a subquery

I created a subquery based on the Role2 type, joined it with the "SomeEntity" entity and applied the predicates. Then I connected the subquery to the "main" query using the Role2.ids that match the subquery predicates.

CriteriaBuilder cb = ...
CriteriaQuery<String> query = cb.createQuery(String.class);
Root<User> user = query.from(User.class);
SetJoin<User, Role> userRolesJoin = user.join(User_.roles);
Path<String> roleIdPath = userRolesJoin.get(User_.id);

Subquery<String> subquery = query.subquery(String.class);
Root<Role2> role2Root = subquery.from(Role2.class);
Join<Role2, SomeEntity> someEntityJoin = role2Root.join(Role2_.someEntity);
Path<String> someEntityPropertyPath = someEntityJoin.get(SomeEntity_.aProperty);
Predicate someEntityPropertyPredicate = cb.equal(someEntityPropertyPath,
            "a property value");
subquery.select(role2Root.get(Role2_.id));
subquery.where(someEntityPropertyPredicate);

In<String> idInSubqueryIds = cb.in(roleIdPath).value(subquery);

query.select(user.get(User_.username));
query.where(idInSubqueryIds);

EntityManager entityManager = ...;
TypedQuery<String> query = entityManager.createQuery(query);
List<String> usernames = query.getResultList();
like image 111
René Link Avatar answered Oct 20 '22 06:10

René Link


You can use as() in Criteria to cast.

((Path)user.join(User_.roles).as(Role2.class)).join(Role2_.someEntity)

This may be provider specific previous to JPA 2.1, but I think it is standard in JPA 2.1.

In JPQL you can use the TREAT operation.

http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/JPQL#Special_Operators

You can also use a second from(),

Root<Role2> role2 = query.from(Role2.class);
... cb.equal(user.join(User_.roles), role2)
like image 2
James Avatar answered Oct 20 '22 08:10

James