JPQL, как присоединиться к выборке списка с любым условием соответствия списку

Данные сущности:

@Entity
@Data
public class Collection {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  private String createdBy;

  @OneToMany(mappedBy = "collection")
  private List<CollectionAccess> collectionAccesses;

}

@Entity
@Data
public class CollectionAccess {

  @Id
  @GeneratedValue(strategy = GenerationType.IDENTITY)
  private Long id;

  @ManyToOne
  @JoinColumn(
      foreignKey =
          @ForeignKey(
              value = ConstraintMode.PROVIDER_DEFAULT,
              name = "fk_collectionaccess_collection_collections_id"))
  private Collection collection;

  private Integer accessType;
  private LocalDateTime expirationAtUtc;
}

Данная база данных:

select id, created_by from collection;
+----+------------+
| id | created_by |
+----+------------+
| 40 |   ABC123   |
+----+------------+

select * from collection_access;
+----+-------------+----------------------------+---------------+
| id | access_type | expiration_at_utc          | collection_id |
+----+-------------+----------------------------+---------------+
|  2 |           0 | 2011-12-03 03:15:30.000000 |            40 |
|  3 |           1 | 2011-12-03 03:15:30.000000 |            40 |
+----+-------------+----------------------------+---------------+

Как вы видите, коллекция 40 имеет 2 Collection_access 2 и 3.

Я хочу сделать JPQL select а Collection, который:

  1. Имеет значение идентификатора 40 И
  2. join fetch это CollectionAccess с условием: если (createdBy совпадает) ИЛИ (любой CollectionAccess имеет accessType = 1 И expirationAtUtc <= now). Зачем присоединяться к Fetch? Из-за N + 1
  3. Условия против CollectionAccess предназначены для сопоставления Collection, а НЕ для фильтрации любых CollectionAccess внутри Collection

(JOIN FETCH предотвращает проблему с запросами N + 1):

createQuery(
      "SELECT c FROM Collection c "
          + "LEFT JOIN FETCH c.collectionAccesses ca WHERE c.id = ?1 "
          + "AND (c.createdBy = ?2 OR (ca.accessType = ?3 AND ca.expirationAtUtc <= ?4))",
      Collection.class)
  .setParameter(1, id)
  .setParameter(2, "DUMMY DATA")
  .setParameter(3, 1)
  .setParameter(4, now)
  getSingleResult();

Я ожидаю, что после выполнения этого запроса я получу Collection с идентификатором 40 и его ДВА CollectionAccess, но он даст мне ТОЛЬКО ОДИН CollectionAccess с идентификатором 3. ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ, ЧТО Я УСТАНОВИЛ ФАКЕТНЫЕ/НЕПРАВИЛЬНЫЕ ДАННЫЕ ?2

Я провел небольшое исследование и нашел эту тему Stackoverflow (основная идея в том, что у нас будут как JOIN, так и JOIN FETCH).

Исходя из этого, я изменил запрос на:

createQuery("SELECT c FROM Collection c LEFT JOIN c.collectionAccesses ca "
          + "LEFT JOIN FETCH c.collectionAccesses WHERE c.id = ?1 "
          + "AND (c.createdBy = ?2 OR (ca.accessType = ?3 AND ca.expirationAtUtc <= ?4))",
      Collection.class)
  .setParameter(1, id)
  .setParameter(2, "ABC123")  //NOW I SET CORRECT DATA TO ?2
  .setParameter(3, 1)
  .setParameter(4, now)
  getSingleResult();

Теперь оно возвращается ко мне ДУБЛИЦИРОВАННОЕ кажется, это неправильно!!

Тогда я решил написать вложение select и надеюсь, что оно сработает:

createQuery(
      "SELECT t FROM (SELECT c FROM Collection c "
              + "LEFT JOIN c.collectionAccesses ca WHERE c.id = ?1 "
              + "AND (c.createdBy = ?2 OR (ca.accessType = ?3 AND ca.expirationAtUtc <= ?4))) t "
              + "JOIN FETCH t.collectionAccesses ",
      Collection.class)
  .setParameter(1, id)
  .setParameter(2, "ABC123")
  .setParameter(3, 1)
  .setParameter(4, now)
  getSingleResult();

НО НЕ УДАЧИ:

java.lang.IllegalArgumentException: org.hibernate.query.SemanticException: Select item at position 1 in select list has no alias (aliases are required in CTEs and in subqueries occurring in from clause)
    at org.hibernate.internal.ExceptionConverterImpl.convert(ExceptionConverterImpl.java:143) ~[hibernate-core-6.4.4.Final.jar:6.4.4.Final]

Как мне заархивировать эту цель?

Я хочу, чтобы JPQL выбрал коллекцию, которая:

  1. Имеет значение идентификатора 40 И
  2. join получает свой CollectionAccess с условием: if (createdBy match) ИЛИ (любой CollectionAccess имеет accessType = 1 И expirationAtUtc <= сейчас)
  3. Условия для CollectionAccess предназначены для сопоставления Collection, а НЕ для фильтрации любого CollectionAccess внутри. Коллекция

Я ожидаю, что после выполнения этого запроса я получу Collection с идентификатором 40 и его ДВА CollectionAccess

Возможно ли это через JPQL? Если нет, не могли бы вы предложить решение?

Пожалуйста помогите, спасибо большое!!

=============================================== ======
ОБНОВЛЯТЬ: Используйте Exists, как предложил Оливье, работает, но генерируется 2 запроса. Может ли это вызвать проблемы с производительностью?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
0
192
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Извините, если я неправильно понял ваши вопросы, но это была ваша первая попытка.

Я ожидаю, что после выполнения этого запроса я получу коллекцию с идентификатором 40 и ее ДВА CollectionAccess, но он даст мне ТОЛЬКО ОДИН CollectionAccess с идентификатором 3. ПОЖАЛУЙСТА, ОБРАТИТЕ ВНИМАНИЕ, ЧТО Я УСТАНОВИЛ ФАКТНЫЕ/НЕПРАВИЛЬНЫЕ ДАННЫЕ НА?2

Почему мы ожидали здесь 2 записи? Я ожидаю, что запрос будет оценен как

"SELECT c FROM Collection c "
      + "LEFT JOIN FETCH c.collectionAccesses ca WHERE c.id = 40 "
      + "AND (c.createdBy = "WrongData" OR (ca.accessType = 1 AND ca.expirationAtUtc <= Now))"

поскольку для c.createdBy установлено что-то неправильное, будут возвращены только те данные, которые оцениваются как TRUE для второго подпункта условия OR. Это будет только одна строка, верно?

Но я выбираю Collection, а не CollectionAccess. Hibernate не должен отфильтровывать CollectionAccess

SoT 11.06.2024 13:00

Чтобы понять это поведение, вам нужно выйти за рамки ORM.

Сгенерированный SQL-запрос/результат будет выглядеть примерно так:

select * from collection c
LEFT OUTER JOIN collectionAccesses ca 
on c.id = ca.collection_id 
WHERE c.id = ?1
AND (c.createdBy = ?2 OR (ca.accessType = ?3 AND ca.expirationAtUtc <= ?4))

    -----+------------+-------+----------------+----------------------------+---------------+
    c.id | c.createdBy| ca.id | ca.access_type | ca.expiration_at_utc       | ca.collection_id |
    -----+------------+-------+----------------+----------------------------+---------------+
      40 |     ABC123 |  2    |              0 | 2011-12-03 03:15:30.000000 |            40 |
      40 |     ABC123 |  3    |              1 | 2011-12-03 03:15:30.000000 |            40 |
    -----+------------+-------+----------------+----------------------------+---------------+

getSingleResult() возвращает одну запись из базы данных, в зависимости от поставщика базы данных добавляет подсказку, например limit 1. Таким образом, вы получите один экземпляр коллекции и один экземпляр коллекцииAccess:

        -----+------------+-------+----------------+----------------------------+---------------+
        c.id | c.createdBy| ca.id | ca.access_type | ca.expiration_at_utc       | ca.collection_id |
        -----+------------+-------+----------------+----------------------------+---------------+
          40 |     ABC123 |  2    |              0 | 2011-12-03 03:15:30.000000 |            40 |            
        -----+------------+-------+----------------+----------------------------+---------------+

Поскольку ваш запрос возвращает только один элемент коллекции (из-за условия id), вы можете использовать метод getResults() и вернуть первый объект.

Надеюсь это поможет.

но почему во втором случае меня продублировали colllection_access?

SoT 25.06.2024 14:14

Hibernate не возвращает отдельные результаты для запроса с включенной внешней выборкой соединения для коллекции. Это объясняется здесь самим Гэвином Кингом: developer.jboss.org/docs/…

Habib Z. 25.06.2024 16:18

«В зависимости от поставщика базы данных будет добавлена ​​подсказка типа ограничения 1» Нет, getSingleResult() выдает NonUniqueResultException, если имеется более одного результата.

Olivier 25.06.2024 16:46

@HabibZ. Вашей ссылке 14 лет. Все меняется со временем. Я считаю, что они решили эту проблему.

talex 30.06.2024 11:10
Ответ принят как подходящий

Вы можете использовать подзапрос EXISTS, чтобы проверить, соответствует ли хотя бы один доступ к коллекции критериям:

List<Collection> list = entityManager.createQuery("SELECT DISTINCT c FROM Collection c "
        + "LEFT JOIN FETCH c.collectionAccesses "
        + "WHERE c.id = ?1 "
        + "AND (c.createdBy = ?2 OR EXISTS (SELECT 1 FROM CollectionAccess ca WHERE ca.collection.id = c.id AND ca.accessType = ?3 AND ca.expirationAtUtc <= ?4))",
        Collection.class)
    .setParameter(1, id)
    .setParameter(2, "DUMMY DATA")
    .setParameter(3, 1)
    .setParameter(4, now)
    .getResultList();

Привет @Оливье, я хотел бы спросить тебя, почему getResultList(), а не getSingleResult()? Кстати, я вижу 2 запроса, которые мне действительно не нужны. Можем ли мы найти лучшее решение? Спасибо

SoT 26.06.2024 09:19

@SoT getSingleResult() выдает NoResultException, когда нет результата, что может произойти здесь. Вот почему вам следует использовать getResultList().

Olivier 26.06.2024 09:29

@SoT «Кстати, я вижу 2 запроса» В моем ответе есть один запрос. Вы это проверяли?

Olivier 26.06.2024 09:29

Да, 1 запрос, но внутри есть подзапрос. Интересно, сколько запросов генерирует jpa (я еще не тестировал, сделаю в ближайшие часы)

SoT 26.06.2024 10:54

есть 2 запроса. Пожалуйста, проверьте мое ОБНОВЛЕНИЕ

SoT 27.06.2024 11:48

@SoT Я вижу на вашем скриншоте только один SQL-запрос.

Olivier 27.06.2024 12:04

строка 8 снизу вверх. Это считается запросом? может ли это повлиять на производительность? Пожалуйста, помогите! Спасибо

SoT 27.06.2024 12:17

@SoT Это подзапрос, но есть еще один SQL-запрос. И на самом деле вы не можете этого избежать, потому что, чтобы проверить, соответствует ли «хотя бы один доступ к коллекции критериям», вам нужно прочитать доступы к коллекции и проверить их критерии, что и делает подзапрос.

Olivier 27.06.2024 12:29

Другие вопросы по теме