Данные сущности:
@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
, который:
join fetch
это CollectionAccess
с условием: если (createdBy
совпадает) ИЛИ (любой CollectionAccess
имеет accessType = 1
И expirationAtUtc <= now
). Зачем присоединяться к Fetch? Из-за N + 1CollectionAccess
предназначены для сопоставления 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 выбрал коллекцию, которая:
- Имеет значение идентификатора 40 И
- join получает свой CollectionAccess с условием: if (createdBy match) ИЛИ (любой CollectionAccess имеет accessType = 1 И expirationAtUtc <= сейчас)
- Условия для CollectionAccess предназначены для сопоставления Collection, а НЕ для фильтрации любого CollectionAccess внутри. Коллекция
Я ожидаю, что после выполнения этого запроса я получу
Collection
с идентификатором 40 и его ДВАCollectionAccess
Возможно ли это через JPQL? Если нет, не могли бы вы предложить решение?
Пожалуйста помогите, спасибо большое!!
=============================================== ======
ОБНОВЛЯТЬ:
Используйте Exists
, как предложил Оливье, работает, но генерируется 2 запроса. Может ли это вызвать проблемы с производительностью?
Извините, если я неправильно понял ваши вопросы, но это была ваша первая попытка.
Я ожидаю, что после выполнения этого запроса я получу коллекцию с идентификатором 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. Это будет только одна строка, верно?
Чтобы понять это поведение, вам нужно выйти за рамки 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
?
Hibernate не возвращает отдельные результаты для запроса с включенной внешней выборкой соединения для коллекции. Это объясняется здесь самим Гэвином Кингом: developer.jboss.org/docs/…
«В зависимости от поставщика базы данных будет добавлена подсказка типа ограничения 1» Нет, getSingleResult() выдает NonUniqueResultException
, если имеется более одного результата.
@HabibZ. Вашей ссылке 14 лет. Все меняется со временем. Я считаю, что они решили эту проблему.
Вы можете использовать подзапрос 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 getSingleResult() выдает NoResultException
, когда нет результата, что может произойти здесь. Вот почему вам следует использовать getResultList()
.
@SoT «Кстати, я вижу 2 запроса» В моем ответе есть один запрос. Вы это проверяли?
Да, 1 запрос, но внутри есть подзапрос. Интересно, сколько запросов генерирует jpa (я еще не тестировал, сделаю в ближайшие часы)
есть 2 запроса. Пожалуйста, проверьте мое ОБНОВЛЕНИЕ
@SoT Я вижу на вашем скриншоте только один SQL-запрос.
строка 8 снизу вверх. Это считается запросом? может ли это повлиять на производительность? Пожалуйста, помогите! Спасибо
@SoT Это подзапрос, но есть еще один SQL-запрос. И на самом деле вы не можете этого избежать, потому что, чтобы проверить, соответствует ли «хотя бы один доступ к коллекции критериям», вам нужно прочитать доступы к коллекции и проверить их критерии, что и делает подзапрос.
Но я выбираю Collection, а не CollectionAccess. Hibernate не должен отфильтровывать CollectionAccess