问题
I am trying to use the JPA CriteriaBuilder to generate a query for an entity called "TestContact" that has a many-to-many join with another entity called "SystemGroup" where the attribute for this join called "groups". The objective of the query is to retrieve records from the "TestContact" entity where the "groups" attribute is either in a list or is empty.
The code I'm using is as follows
public List<TestContact> findWithCriteriaQuery(List<SystemGroup> groups) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<TestContact> cq = cb.createQuery(TestContact.class);
Root<TestContact> testContact = cq.from(TestContact.class);
cq.select(testContact);
Path<List<SystemGroup>> groupPath = testContact.get("groups");
// cq.where(groupPath.in(groups));
// cq.where(cb.isEmpty(groupPath));
cq.where(cb.or(cb.isEmpty(groupPath), groupPath.in(groups)));
TypedQuery<TestContact> tq = em.createQuery(cq);
return tq.getResultList();
}
The problem is this query only returns results where group is in the list "groups" but for some reason isn't also returning the results where group is empty (i.e. there is no entry in the join table)
If I change the where clause to cq.where(cb.isEmpty(groupPath));
then the query correctly returns the results where group is empty.
If I change the where clause to cq.where(groupPath.in(groups));
then the query correctly returns the results where the group is in the list "groups".
What I don't understand is why when I try to combine these two predicates using the CriteriaBuilder or method the results don't include the records where the group is either in the list or is empty.
The groups attribute in the "TestContact" entity is declared as follows
@ManyToMany(fetch=FetchType.EAGER)
@JoinTable(name = "TEST_CONTACT_GROUPS", joinColumns = { @JoinColumn(name = "CONTACT_ID", referencedColumnName = "CONTACT_ID") }, inverseJoinColumns = { @JoinColumn(name = "GROUP_ID", referencedColumnName = "GROUP_ID") })
private List<SystemGroup> groups;
The JPA provider is EclipseLink 2.5.0, the Java EE application server is GlassFish 4 and the database is Oracle 11gR2.
Can anyone please point out where I'm going wrong?
Update
I've tried the suggestion from @Chris but Eclipse is returning the following error on Join<List<SystemGroup>> groupPath = testContact.join("groups", JoinType.LEFT)
Incorrect number of arguments for type Join; it cannot be parameterized with arguments >
Looking at the JavaDoc for Join
it says the type parameters are...
Z - the source type of the join, X - the target type of the join
I've tried Join<TestContact, SystemGroup> groupPath = testContact.join("groups", JoinType.LEFT);
which then causes Eclipse to return the following error on cb.isEmpty
Bound mismatch: The generic method isEmpty(Expression) of type CriteriaBuilder is not applicable for the arguments (Join). The inferred type SystemGroup is not a valid substitute for the bounded parameter >
回答1:
The testContact.get("groups"); clause forces an inner join from testContact to groups, which filters out testContacts with no groups. You need to specify a left outer join, and use that in your isEmpty and in clauses.
Root<TestContact> testContact = cq.from(TestContact.class);
cq.select(testContact);
Join<TestContact, SystemGroup> groupPath = testContact.join("groups", JoinType.LEFT);
cq.where(cb.or(cb.isEmpty(testContact.get("groups")), groupPath.in(groups)));
I usually refer to https://en.wikibooks.org/wiki/Java_Persistence/Criteria#Join for examples
来源:https://stackoverflow.com/questions/25554920/using-jpa-criteriabuilder-to-generate-query-where-attribute-is-either-in-a-list