Использование JPA CriteriaBuilder для создания запроса, где атрибут либо находится в списке, либо пуст

Я пытаюсь использовать JPA CriteriaBuilder для создания запроса для объекта под названием «TestContact», который имеет соединение «многие ко многим» с другим объектом под названием «SystemGroup», где атрибут для этого соединения называется «группы». Цель запроса — извлечь записи из сущности «TestContact», где атрибут «groups» либо находится в списке, либо пуст.

Код, который я использую, выглядит следующим образом

public List<TestContact> findWithCriteriaQuery(List<SystemGroup> groups) {

    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery<TestContact> cq = cb.createQuery(TestContact.class);
    Root<TestContact> testContact = cq.from(TestContact.class);
    cq.select(testContact);

    Path<List<SystemGroup>> groupPath = testContact.get("groups");

    // cq.where(groupPath.in(groups));
    // cq.where(cb.isEmpty(groupPath));
    cq.where(cb.or(cb.isEmpty(groupPath), groupPath.in(groups)));

    TypedQuery<TestContact> tq = em.createQuery(cq);

    return tq.getResultList();
}

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

Если я изменю предложение where на cq.where(cb.isEmpty(groupPath));, тогда запрос правильно вернет результаты, где группа пуста.

Если я изменю предложение where на cq.where(groupPath.in(groups));, тогда запрос правильно вернет результаты, в которых группа находится в списке «группы».

Чего я не понимаю, так это почему, когда я пытаюсь объединить эти два предиката с помощью CriteriaBuilder или метода, результаты не включают записи, где группа либо находится в списке, либо пуста.

Атрибут групп в объекте «TestContact» объявляется следующим образом.

@ManyToMany(fetch=FetchType.EAGER)
@JoinTable(name = "TEST_CONTACT_GROUPS", joinColumns = { @JoinColumn(name = "CONTACT_ID", referencedColumnName = "CONTACT_ID") }, inverseJoinColumns = { @JoinColumn(name = "GROUP_ID", referencedColumnName = "GROUP_ID") })
private List<SystemGroup> groups;

Поставщик JPA — EclipseLink 2.5.0, сервер приложений Java EE — GlassFish 4, а база данных — Oracle 11gR2.

Может ли кто-нибудь указать, где я ошибаюсь?

Обновить

Я попробовал предложение @Chris, но Eclipse возвращает следующую ошибку на Join<List<SystemGroup>> groupPath = testContact.join("groups", JoinType.LEFT)

Неверное количество аргументов для типа Join; его нельзя параметризовать аргументами >

Глядя на JavaDoc для Join, он говорит, что параметры типа...

Z - исходный тип соединения, X - целевой тип соединения

Я пробовал Join<TestContact, SystemGroup> groupPath = testContact.join("groups", JoinType.LEFT);, что затем заставляет Eclipse возвращать следующую ошибку на cb.isEmpty

Несоответствие привязки: универсальный метод isEmpty(Expression) типа CriteriaBuilder неприменим для аргументов (Join). Выведенный тип SystemGroup не является допустимой заменой ограниченного параметра >


person Paul H    schedule 28.08.2014    source источник


Ответы (1)


testContact.get("группы"); Предложение вызывает внутреннее соединение из testContact с группами, что отфильтровывает testContact без групп. Вам нужно указать левое внешнее соединение и использовать его в предложениях isEmpty и in.

Root<TestContact> testContact = cq.from(TestContact.class);
cq.select(testContact);

Join<TestContact, SystemGroup> groupPath = testContact.join("groups", JoinType.LEFT);
cq.where(cb.or(cb.isEmpty(testContact.get("groups")), groupPath.in(groups)));

Обычно я ссылаюсь на https://en.wikibooks.org/wiki/Java_Persistence/Criteria#Join для примеров

person Chris    schedule 29.08.2014
comment
Спасибо. Я попробовал ваше предложение, но получаю сообщение об ошибке Join<List<SystemGroup>> groupPath = testContact.join("groups", JoinType.LEFT);. Я обновил исходный вопрос с некоторыми дополнительными подробностями. Любые идеи? - person Paul H; 29.08.2014
comment
У меня не было тестового проекта, чтобы сначала попробовать. Я обновил образец, который теперь должен работать - я считаю, что JPA должен использовать .get(groups) для создания подзапроса, а внешнее соединение с отношением для предложения "in". - person Chris; 29.08.2014
comment
Eclipse сообщает, что .isEmpty() не является методом testContact.get("groups") . Eclipse предлагает привести его к CriteriaBuilder, но если я попытаюсь это сделать, Eclipse выдаст связанную ошибку несоответствия в методе .isEmpty(). - person Paul H; 30.08.2014
comment
Код, который я использовал в конце, был Root<TestContact> testContact = cq.from(TestContact.class); cq.select(testContact); Path<List<SystemGroup>> groupPath1 = testContact.get("groups"); ListJoin<TestContact, SystemGroup> groupPath2 = testContact.joinList("groups",JoinType.LEFT); cq.where(cb.or(cb.isEmpty(groupPath1),groupPath2.in(groups))); - person Paul H; 26.10.2014