Как составить запрос с помощью Criteriq Query?

Имеются таблицы:

  1. gift_certificate (подарочный сертификат)
  2. tag (тег)
  3. gift_certificate_to_tag_relation (для связи многие-ко-многим между подарочными сертификатами и тегами, содержит ID) введите сюда описание изображения

Как составить запрос с помощью Criteria Query на поиск подарочных сертификатов, которые имеют определенный набор тегов. Теги передаются в Set<String> tagNames.

Java сущности:

  1. GiftCertificate.class:
private Long id;
@Column(nullable = false, unique = true)
private String name;

@Column(nullable = false)
private String description;

@Column(nullable = false)
private BigDecimal price;

@Column(nullable = false)
private Integer duration;

@Column(nullable = false)
private LocalDateTime createDate;

@Column(nullable = false)
private LocalDateTime lastUpdateDate;
  1. Tag.class:
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false, unique = true)
private String name;
  1. GiftCertificateToTagRelation.class:
@Id
@ManyToOne
@JoinColumn(name = DatabaseColumnName.GIFT_CERTIFICATE_ID)
private GiftCertificate giftCertificate;

@Id
@ManyToOne
@JoinColumn(name = DatabaseColumnName.TAG_ID)
private Tag tag;

Что я пробую:

Set<String> tagNames = ...

CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<GiftCertificate> criteriaQuery = builder.createQuery(GiftCertificate.class);
Root<GiftCertificate> root = criteriaQuery.from(GiftCertificate.class);
Root<GiftCertificateToTagRelation> relationRoot = criteriaQuery.from(GiftCertificateToTagRelation.class);
Join<GiftCertificateToTagRelation, Tag> tagJoin = relationRoot.join("tag");
Predicate condition = tagJoin.get("name").in(tagNames);
criteriaQuery.where(condition)
    .groupBy(root)
    .having(builder.count(root).in(tagNames.size()));

В результате данного запроса Hibernate формирует SQL запрос (в случае передачи двух имен тегов):

select *
from gift_certificate gc
         cross join gift_certificate_to_tag_relation relation
         inner join tag tag3_ on relation.tag_id = tag3_.id
where tag3_.name in ('tag1', 'tag2')
group by gc.id
having count(gc.id) in (2)
order by gc.id;

Но данный SQL код возвращает пустой результат. Я обнаружил, что если в строке cross join добавить условие on gc.id = relation.gift_certificate_id, то в результате получается желаемый результат:

select *
from gift_certificate gc
         cross join gift_certificate_to_tag_relation relation on gc.id = relation.gift_certificate_id
         inner join tag tag3_ on relation.tag_id = tag3_.id
where tag3_.name in ('tag1', 'tag2')
group by gc.id
having count(gc.id) in (2)
order by gc.id;

Я понимаю, что условие ON вызывается на объекте Join<?, ?>. Но как тогда c с помощью Criteria Query задать это словие ON (on gc.id = relation.gift_certificate_id)?


Ответы (1 шт):

Автор решения: Taylor

Так как результат преобразования выглядит следущим образом:

select *
from gift_certificate gc
         cross join gift_certificate_to_tag_relation relation
         inner join tag tag3_ on relation.tag_id = tag3_.id
where tag3_.name in ('tag1', 'tag2')
group by gc.id
having count(gc.id) in (2)
order by gc.id;

Здесь в строке cross join gift_certificate_to_tag_relation relation не хватает условия ON, при добавлении которого строка примет вид:

cross join gift_certificate_to_tag_relation relation on gc.id = relation.gift_certificate_id

Для решения данной проблемы с помощью CriteriaQuery необходимо добавить данное условие в строку inner join tag tag3_ on relation.tag_id = tag3_.id, тогда финальный SQL запрос будет выглядеть следующим образом:

select *
from gift_certificate gc
         cross join gift_certificate_to_tag_relation relation 
         inner join tag tag3_ on relation.tag_id = tag3_.id AND gc.id = relation.gift_certificate_id
where tag3_.name in ('tag1', 'tag2')
group by gc.id
having count(gc.id) in (2)
order by gc.id;

Переводим это на CriteriaQuery и получаем:

Root<GiftCertificateToTagRelation> relation = query.from(GiftCertificateToTagRelation.class);
Join<GiftCertificateToTagRelation, Tag> rel = relation.join(ParameterName.TAG);
Predicate on = builder.equal(root, relation.get(ParameterName.GIFT_CERTIFICATE));
rel.on(on); // inner join tag t on rel.tag_id = t.id and (gc.id = rel.gift_certificate_id)
Predicate condition = rel.get(ParameterName.NAME).in(tagNames);
→ Ссылка