Reputation: 10131
I have two entities in a @ManyToMany
relationship.
// Output has 4 other @ManyToOne relationships if that matters
@Entity @Table public class Output {
@Id public String address;
@ManyToMany(targetEntity = Interval.class,
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
@JoinTable(name = "output_has_interval",
joinColumns = {@JoinColumn(name = "output_address",
referencedColumnName = "address")},
inverseJoinColumns = {@JoinColumn(name = "interval_start",
referencedColumnName = "start"),
@JoinColumn(name = "interval_end",
referencedColumnName = "end")})
Collection<Interval> intervals;
@IdClass(IntervalPK.class) // I'll omit this one.
@Entity @Table public class Interval {
@Id public Calendar start;
@Id public Calendar start;
@ManyToMany(targetEntity = Output.class,
mappedBy = "intervals",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
public Collection<Output> outputs;
The join table is called output_has_interval
between output
and interval
.
How do I do CriteriaQuery
like this?
SELECT `output`.`address`
FROM `output`, `output_has_interval`, `interval`
WHERE `output`.`address` = `output_has_interval`.`output_address`
AND `interval`.`start` = `output_has_interval`.`interval_start`
AND `interval`.`end` = `output_has_interval`.`interval_end`
AND `interval`.`start` >= '2011-04-30'
This works as expected if I issue it in MySQL.
(I have the corresponding static meta model classes as well, on request I'll could post them - nothing fancy tho'.)
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Output> cq = cb.createQuery(Output.class);
Root<Output> root= cq.from(Output.class);
CollectionJoin<Output, Interval> join = root.join(Output_.intervals);
Expression<Calendar> start = join.get(Interval_.start);
Predicate pred = cb.greaterThanOrEqualTo(start, /* calendar for '2011-04-30' */);
cq.where(pred);
TypedQuery<Output> tq = em.createQuery(cq);
However tq.getResultList
returns every output
row from my database. Any idea?
(On a side note: Hibernate (the provider I'm using) generates many select
statements when I issue this query, one for every relationship Output
has, sometimes more.)
Edit.: I wrote:
tq.getResultList
returns everyoutput
row from my database
To clarify it: it returns more than just every output
row from my database. It actually does a join using output
and interval
however the predicate:
`interval`.`start` >= '2011-04-30'
doesn't get satisfied.
Upvotes: 2
Views: 9924
Reputation: 10131
Ok, I'll managed to solve my riddle on my own.
First of all: the whole problem originated from the fact that I'm a lousy programmer. I iterated over TypedQuery<Output>.getResultList()
and accessed every Interval
in Output.intervals
in a recursive manner, thus Hiberate loaded lazily the requested objects generating a handful of select
statements.
However I had to get a hold of those Interval
instaces somehow. The following change to my CriteriaQuery
did the trick.
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Tuple> cq = cb.createTupleQuery(); // or createQuery(Tuple.class)
Root<Output> root= cq.from(Output.class); // from clause
CollectionJoin<Output, Interval> join = root.join(Output_.intervals);
Path<String> addressPath = root.get(Output_.address); // mind these Path objects
Path<Calendar> startPath = join.get(Interval_.start); // these are the key to success!
cq.multiselect(addressPath, startPath); // select clause
Expression<Calendar> start = join.get(Interval_.start);
Predicate pred = cb.greaterThanOrEqualTo(start, /* calendar for '2011-04-30' */);
cq.where(pred); // where clause
TypedQuery<Tuple> tq = em.createQuery(cq); // holds Tuples
for (Tuple tuple : tq.getResultsList()) {
String address = tuple.get(addressPath);
Calendar start = tuple.get(startPath);
...
I've just realized that I could've used Path<T>
objects instead Expression<T>
objects (or vice versa) as Path<T>
extends Expression<T>
. Oh well...
Upvotes: 2