JPA 2.0 CriteriaQuery для таблиц в отношении @ManyToMany - PullRequest
2 голосов
/ 12 апреля 2011

У меня есть две сущности в отношениях @ManyToMany.

// 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;

Таблица соединений называется output_has_interval между output и interval.

Как мне сделать CriteriaQuery как это?

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'

Это работает, как и ожидалось, если я выпущу его в MySQL.

(У меня также есть соответствующие классы статической метамодели, по запросу я могу их опубликовать - ничего особенного.)

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);

Однако tq.getResultList возвращает каждую строку output из моей базы данных. Есть идеи?

(С другой стороны: Hibernate (поставщик, которого я использую) генерирует много select операторов, когда я выполняю этот запрос, по одному на каждое отношение, Output имеет, иногда больше.)

Ред .: Я написал:

tq.getResultList возвращает каждый output строка из моей базы данных

Для пояснения: он возвращает больше, чем просто каждую строку output из моей базы данных. На самом деле соединение выполняется с использованием output и interval, однако предикат:

`interval`.`start` >= '2011-04-30'

не устраивает.

1 Ответ

2 голосов
/ 13 апреля 2011

Хорошо, мне удалось решить мою загадку самостоятельно.

Прежде всего: вся проблема возникла из-за того, что я паршивый программист. Я перебрал TypedQuery<Output>.getResultList() и получил доступ к каждому Interval в Output.intervals рекурсивным способом, поэтому Hiberate лениво загружал запрошенные объекты, генерируя несколько select операторов.

Однако мне нужно было как-то ухватиться за эти Interval парни. Следующее изменение моего CriteriaQuery добилось цели.

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);
...

Редактировать

Я только что понял, что мог бы использовать Path<T> объекты вместо Expression<T> объектов (или наоборот), так как Path<T> расширяет Expression<T>. Ну да ладно ...

...