Как заставить CriteriaBuilder присоединиться к пользовательскому условию?

Я хочу сделать запрос, в котором я соединяю 2 таблицы, используя CriteriaBuilder. В MySQL запрос, который я пытаюсь сделать, будет выглядеть так:

SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
AND item.type_id = 1

Я хочу получить все заказы, и если у них есть предмет типа №1, я хочу присоединиться к этому предмету. Однако, если элемент типа № 1 не найден, я все равно хочу получить заказ. Я не могу понять, как это сделать с помощью CriteriaBuilder. Все, что я умею делать, это:

CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Order> cq = cb.createQuery(Order.class);
Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
Join<Item, Type> type = order.join(Item_.type, JoinType.LEFT);
cq.select(order);
cq.where(cb.equal(type.get(Type_.id), 1));

Этот запрос не работает, так как он приводит к чему-то вроде этого в MySQL:

SELECT * FROM order
LEFT JOIN item
ON order.id = item.order_id
WHERE item.type_id = 1

Результат будет содержать только заказы с товарами типа #1. Заказы без исключены. Как я могу использовать CriteriaBuilder для создания запроса, как в первом примере?


person Bjørn Stenfeldt    schedule 13.04.2013    source источник
comment
Я хочу получить все заказы, и если у них есть элемент типа #1, этот элемент должен быть включен в результат Какой тип результата вы хотите? Заказ? Или кортеж/мультиселект? Чего вы хотите больше, чем всех заказов? Пожалуйста, объясни   -  person perissf    schedule 14.04.2013
comment
Извините, я не очень ясно выразился. Я исправил описание. На самом деле я не заинтересован в получении предмета как части моего результата. Я просто хочу заказы в моем результате. По сути, я хочу отсортировать результат на основе элементов определенного типа, но при этом получать заказы без элементов этого типа.   -  person Bjørn Stenfeldt    schedule 14.04.2013
comment
Это возможно с CriteriaBuilder! Посмотрите на мой ответ ниже   -  person hzitoun    schedule 29.07.2016


Ответы (5)


Это возможно с версии 2.1 JPA, включенной с использованием on метода Join<Z, X> on(Predicate... restrictions);

Вот как:

Root<Order> order = cq.from(Order.class);
Join<Order, Item> item = order.join(Order_.itemList, JoinType.LEFT);
item.on(cb.equal(item.get(Item_.type), 1));
person hzitoun    schedule 23.06.2016
comment
Если это сработает, то будет довольно много нативных запросов, которые можно будет заменить. Я попробую это в следующий раз, когда мне понадобится присоединение. - person Bjørn Stenfeldt; 24.06.2016
comment
Я не понял, что вы имеете в виду под несколькими нативными запросами, которые можно заменить! Попробуйте сначала, и вы увидите, что это работает отлично! - person hzitoun; 29.06.2016
comment
Я просто имел в виду, что у меня много мест, где я использую entityManager.createNativeQuery(String) из-за недостатков CriteriaBuilder. :-) - person Bjørn Stenfeldt; 29.06.2016
comment
@Mitchapp, помогите мне, пожалуйста, создать аналогичный запрос. Взгляните на этот вопрос, пожалуйста, stackoverflow.com/questions/ 46793743/ - person VB_; 18.10.2017

Я думаю, что это та же проблема, что и в этом вопросе. Похоже, что это невозможно в CriteriaBuilder. Это возможно в Hibernate Criteria API, но это, вероятно, вам не поможет.

JPA Criteria API: множественное условие для LEFT JOIN

person carbontax    schedule 14.04.2013
comment
Первый MySQL — это то, что я хочу, чтобы мой CriteriaBuilder сгенерировал. Второй MySQL представляет то, что генерирует показанный CriteriaQuery. Если я просто добавлю or item.id is null ко второму запросу MySQL, я не получу то, что хочу. Это исключит заказы с элементами, у которых type_id отличается от 1, и вернет только заказы с элементами типа 1 или вообще без элементов. - person Bjørn Stenfeldt; 14.04.2013
comment
Спасибо. Я мог бы попробовать в меньшем масштабе, но в моем текущем проекте это, вероятно, мне не поможет. Сейчас я на EclipseLink (JPA 2.0). Насколько мне удалось выяснить (ссылка), функция I need не будет доступен до JPA 2.1. Надеюсь, это будет скоро. - person Bjørn Stenfeldt; 14.04.2013
comment
Это возможно с CriteriaBuilder! Посмотри на мой ответ сразу под твоим - person hzitoun; 29.06.2016

Я знаю, что этот вопрос был задан давно, но недавно у меня была такая же проблема, и я нашел это решение на форуме Oracle, я скопировал и вставил его на тот случай, если ссылка больше недоступна.

MiguelChillitupaArmijos 29 апреля 2011 г. 1:41 (в ответ на 840578) Думаю, вам следует использовать что-то вроде:

em.createQuery("SELECT DISTINCT e.Id" +
                    " from Email e " +
                    " left join e.idEmailIn e2 *with* e2.responseType = 'response'" +
                    "     where e.type = 'in' and e.responseMandatory = true").getSingleResult(); 

А это ссылка.

Критерии JPA: ЛЕВОЕ СОЕДИНЕНИЕ с условием И

person OJVM    schedule 28.05.2015

Существует обходной путь, если вы используете Hibernate 3.6 с JPA 2.0. Это не лучшее решение, однако оно идеально подходит для меня.

Я продублировал объект с аннотацией @Where hibernate. Это означает, что каждый раз, когда вы используете соединение с этим объектом, hibernate будет добавлять дополнительное условие в оператор соединения при сгенерированном SQL.

Например, изначально у нас есть следующий пример:

@Entity
@Table(name = "PERSON")
public class Person {

    @Id
    @Column(name = "PERSON_ID")
    private Long id;

    @Id
    @Column(name = "PERSON_NAME")
    private String name;

   @OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
   private Set<Address> addresses;

}

@Entity
@Table(name = "ADDRESS")
public class Address {

    @Id
    @Column(name = "ADDRESS_ID")
    private Long id;

    @Id
    @Column(name = "ADDRESS_STREET")
    private String street;

   @ManyToOne
   @JoinColumn(name = "PERSON_ID")
    private Person person;

}

Чтобы добавить дополнительные условия для критериев Join, нам нужно продублировать сопоставление Address @Entity, добавив аннотацию @Where @Where(clause = "ADDRESS_TYPE_ID = 2").

@Entity
@Table(name = "ADDRESS")
@Where(clause = " ADDRESS_TYPE_ID = 2")
public class ShippingAddress {

    @Id
    @Column(name = "ADDRESS_ID")
    private Long id;

    @Id
    @Column(name = "ADDRESS_STREET")
    private String street;

   @OneToOne
   @JoinColumn(name = "PERSON_ID")
    private Person person;

}

Кроме того, нам нужно добавить дублирующую ассоциацию сопоставления для нового объекта.

@Entity
@Table(name = "PERSON")
public class Person {

    @Id
    @Column(name = "PERSON_ID")
    private Long id;

    @Id
    @Column(name = "PERSON_NAME")
    private String name;

   @OneToMany(mappedBy = "person", fetch = FetchType.LAZY)
   private Set<Address> addresses;

   @OneToOne(mappedBy = "person")
   private ShippingAddress shippingAddress;

}

Наконец, вы можете использовать соединение с этим конкретным объектом в своих критериях:

PersonRoot.join(Person_.shippingAddress, JoinType.LEFT);

Hibernate Snippet SQL должен выглядеть так:

 left outer join
        address shippingadd13_ 
            on person11_.person_id=shippingadd13_.person_id 
            and (
                shippingadd13_.ADDRESS_TYPE_ID = 2 
            ) 
person Eduardo Fabricio    schedule 29.06.2015
comment
Есть ли способы параметризовать @Where(clause = " ADDRESS_TYPE_ID = 2") ? - person Alexander; 14.05.2021

Предложение ON поддерживается в версии Hibernate 4.3. Кто-нибудь знает, существует ли проблема индексации параметров между индексом параметра дополнительных пользовательских условий и индексом существующих фильтров сопоставления при выполнении внешнего соединения с предложением ON?

Используя приведенный ниже класс сущности Person в качестве примера, предположим, что я добавляю этот фильтр, чтобы ограничить типы адресов, и фильтр включен для заполнения предложения IN. Индекс параметра для предложения IN вызовет проблему [2], когда я добавлю дополнительные условия (например, использование столбца «улица») в часть предложения ON. Это известная проблема?

[1] @Filter(имя = "addressTypes", условие = "ADDRESS_TYPE в (:supportedTypes)")

[2] Причина: ОШИБКА 22018: недопустимый формат символьной строки для типа BIGINT. приватный набор адресов;

person user3390708    schedule 29.11.2016