Reputation: 97
I have two classes, Account
and Admin
, with many to many mapping.
The Admin
class has a collection of Account
class and vise versa.
I want to write a query, that given the account id, will return all the account admins.
Here is the relevant fields of the Account
class:
@Entity
public class Account {
@Id
public Long id;
@ManyToMany(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
public List<Admin> users = new ArrayList<>();
}
I have tried a regular query for Admin.class
with multiselect
as each account has a collection of admins, but trying to get a TypedQuery<Admin>
out of my CriteriaQuery<Admin>
I got an IllegalArgumentException
with the message "org.hibernate.hql.internal.ast.QuerySyntaxException: Unable to locate appropriate constructor on class [models.Admin]. Expected arguments are: java.util.Collection [select new models.Admin(generatedAlias0.users) from models.Account as generatedAlias0 where generatedAlias0.id=1L]" (1L here probably since I called this function with 1 as accountId), caused by QuerySyntaxException
with the message "Unable to locate appropriate constructor on class [models.Admin]. Expected arguments are: java.util.Collection".
Code:
private static List<Admin> readAccountAdmins(Long accountId) {
CriteriaBuilder cb = JPA.em().getCriteriaBuilder();
CriteriaQuery<Admin> cq = cb.createQuery(Admin.class);
Root<Account> root = cq.from(Account.class);
Predicate idPredicate = cb.equal(root.get(Account_.id), accountId);
cq.multiselect(root.get(Account_.users)).where(idPredicate);
TypedQuery<Admin> typedQuery = JPA.em().createQuery(cq); // exception thrown here
return typedQuery.getResultList();
}
After that I tried running a TypedQuery<List<Admin>>
, as I am trying to read a list. This is the first iteration of trying a query of list:
private static List<Admin> readAccountAdmins(Long accountId) {
CriteriaBuilder cb = JPA.em().getCriteriaBuilder();
CriteriaQuery<List<Admin>> cq = cb.createQuery((Class<List<Admin>>)(Class<?>)(Collection.class));
Root<Account> root = cq.from(Account.class);
Predicate idPredicate = cb.equal(root.get(Account_.id), accountId);
cq.select(root.get(Account_.users)).where(idPredicate);
TypedQuery<List<Admin>> typedQuery = JPA.em().createQuery(cq);
return typedQuery.getSingleResult(); // exception thrown here
}
I used getSingleResult
as getResultList
caused a compilation error, saying the actual return value is List<List<Admin>>>
and doesn't match the signature.
This method threw a different exception, a NonUniqueResultException
with the message: "result returns more than one elements".
While debugging, I tried to evaluate the expression typedQuery.getResultList()
and saw that it actually returns List<Admin>
and not List<List<Admin>>
, so I got to my final iteration of this function:
private static List<Admin> readAccountAdmins(Long accountId) {
CriteriaBuilder cb = JPA.em().getCriteriaBuilder();
CriteriaQuery<List<Admin>> cq = cb.createQuery((Class<List<Admin>>)(Class<?>)(Collection.class));
Root<Account> root = cq.from(Account.class);
Predicate idPredicate = cb.equal(root.get(Account_.id), accountId);
cq.select(root.get(Account_.users)).where(idPredicate);
TypedQuery<List<Admin>> typedQuery = JPA.em().createQuery(cq);
return (List) typedQuery.getResultList();
}
Now, this function works, but my question is why?
Why did the compiler decide that getResultList
returns a different value than the actual return value?
Upvotes: 1
Views: 575
Reputation: 499
Maybe it makes sense when you take a closer look at your database. A TypeQuery
returns entities, so basically rows from tables. List<Admin>
is a collection of Entities, so eventhough your Account
has a List<Admin>
as a field, the Query will still return List<Admin>
entities, not List<List<Admin>>
as List<Admin>
is not an entity.
I hope that makes sense.
Upvotes: 1