Reputation: 166
I am trying to build dynamic queries using JPA and Criteria API but without metamodels.
With the tables:
Foo:
ID
1
2
3
4
Bar:
ID FooID Number
1 2 44
2 2 55
3 3 55
I want to retrieve all Foo entities where no matching Bar has Number 44. (Expecting Foo 1, 3, 4)
The SQL should look something like this:
select distinct *
from Foo foo0_
left join Bar bar0_ on foo0_.ID=bar0_.FooID and bar0_.Number=44
where bar0__.id is null;
My code looks something like this:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Foo> cq = cb.createQuery(Foo.class);
Root<Foo> fooRoot = cq.from(Foo.class);
cq.select(fooRoot).distinct(true);
List<Predicate> allPredicates = new ArrayList<Predicate>();
List<Predicate> orPredicates = new ArrayList<Predicate>();
List<Predicate> andPredicates = new ArrayList<Predicate>();
andPredicates.add(cb.equal(fooRoot.join("bars", JoinType.LEFT).get("number"), 44));
andPredicates.add(cb.isNull(fooRoot.join("bars", JoinType.LEFT).get("ID")));
orPredicates.add(cb.and(andPredicates.toArray(new Predicate[andPredicates.size()])));
allPredicates.add(cb.or(orPredicates.toArray(newPredicate[orPredicates.size()])));
cq.where(allPredicates.toArray(new Predicate[allPredicates.size()]));
TypedQuery<Foo> query = em.createQuery(cq);
query.getResultList();
But that generates this SQL:
select distinct *
from Foo foo0_
left outer join Bar bar1_ on foo0_.id=bar1_.FooID
left outer join Bar bar2_ on foo0_.id=bar2_.FooID
where bar1_.Number=44 and (bar2_.id is null);
and returns no Foo.
Any ideas? Thank you!
Upvotes: 2
Views: 6996
Reputation: 11531
You don't specify which JPA implementation this is for, and clearly the SQL may differ for each.
That aside, your joining is wrong. Each join
call will do a JOIN, and you only want 1 join if what you write is correct. Also you can make use of JPA 2.1 "ON" conditions on the join (rather than putting it in the WHERE).
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Foo> cq = cb.createQuery(Foo.class);
Root<Foo> fooRoot = cq.from(Foo.class);
cq.select(fooRoot).distinct(true);
Join barJoin = fooRoot.join("bars", JoinType.LEFT);
Predicate onCond = cb.equal(barJoin.get("number"), 44);
barJoin.on(onCond);
Then do the same as your question except using the barJoin
; no idea what those AND/OR conditions are trying to do - your suggested SQL has none of that.
Upvotes: 5