Gal Mor
Gal Mor

Reputation: 97

How to read a collection property with JPA?

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

Answers (1)

PaulD
PaulD

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

Related Questions