Присоединитесь к различным элементам, используя Spring спецификации JPA и спящий режим
Я пытаюсь создать спецификацию JPA для реализации I18N, чтобы иметь возможность фильтровать имя.
В базе данных у меня есть переводы для нескольких языков.
Дело в том, что в большинстве случаев указывается только язык по умолчанию.
Поэтому, когда кто-то запрашивает перевод нестандартного языка, я хочу, чтобы он возвращался к языку по умолчанию.
До сих пор я смог построить это в спецификациях
Specification<S> specification = (root, query, cb) -> {
Join i18n = root.join("translations", JoinType.LEFT);
i18n.alias("i18n");
Join i18nDefault = root.join("translations", JoinType.LEFT);
i18nDefault.alias("i18nDefault");
i18n.on(cb.and(cb.equal(i18n.get("itemId"), root.get("itemId")), cb.equal(i18n.get("languageId"), 1)));
i18nDefault.on(cb.and(cb.equal(i18nDefault.get("itemId"), root.get("itemId")), cb.equal(i18nDefault.get("languageId"), 22)));
// Clauses and return stuff
};
Но это вызывает ошибку, которая звучит как плохая новость для этого решения.
org.hibernate.hql.internal.ast.QuerySyntaxException: with-clause referenced two different from-clause elements
[
select generatedAlias0 from com.something.Item as generatedAlias0
left join generatedAlias0.i18n as i18n with (i18n.itemId=generatedAlias0.itemId) and ( i18n.languageId=1L )
left join generatedAlias0.i18n as i18nDefault with (i18nDefault.itemId=generatedAlias0.itemId) and ( i18nDefault.languageId=1L )
];
Итак, Hibernate не позволяет мне создавать с-предложение с разными элементами (в данном случае itemId и languageId).
Есть ли способ, каким образом я могу реализовать это правильно или по-другому?
В конце я хотел бы, чтобы Hibernate генерировал запрос (Oracle), который выглядит как
SELECT *
FROM item i
LEFT JOIN i18n_item i18n ON i.item_id = i18n.item_id AND i18n.language_id = 22
LEFT JOIN i18n_item i18n_default ON i.item_id = i18n_default.item_id AND i18n_default.language_id = 1
// where-clauses;
Решение этого с использованием предложения where
Я видел несколько других связанных вопросов, у которых есть ответы, которые говорят, чтобы использовать предложение where вместо второго критерия соединения.
Дело в том, что переводы для языка, который не является дефолтом, могут отсутствовать для некоторых записей элемента. Поскольку пользователи - это те, которые определяют переводы для своего контента (которые они также управляют).
Например, пользователь мог указать 200 записей перевода для предметов, предназначенных для английского языка, но только 190 записей переводов для предметов, предназначенных для голландцев. Английский язык по умолчанию указан в приведенном выше примере.
Я попытался создать запрос, используя предложение where, но это привело к тому, что количество результатов было непоследовательным. Под этим я подразумеваю, что нефильтрованным результатом будет 200 предметов. Когда я фильтрую имя с помощью like '%a%'
, он вернет 150 результатов, и когда я переверну его фильтр (not like '%a%'
), он вернет что-то вроде 30. Где бы я ожидал, что они добавят до 200.
Это работает
SELECT *
FROM item i
LEFT JOIN i18n_item i18n ON i.item_id = i18n.item_id AND i18n.language_id = 22
LEFT JOIN i18n_item i18n_default ON i.item_id = i18n_default.item_id AND i18n_default.language_id = 1
WHERE
(
(lower(i18n.name) not like '%a%')
or
(i18n.name is null and (lower(i18n_default.name) not like '%a%'))
)
Это не работает (не возвращает правильное количество элементов)
SELECT *
FROM item i
LEFT JOIN i18n_item i18n ON i.item_id = i18n.item_id
LEFT JOIN i18n_item i18n_default ON i.item_id = i18n_default.item_id
WHERE
(
(i18n.language_id = 22 and lower(i18n.name) not like '%a%')
or
(i18n_default.language_id = 1 and i18n.name is null and (lower(i18n_default.name) not like '%a%'))
)
Есть ли способ реализовать запрос, который работает, используя спецификации JPA?
Ответы
Ответ 1
После выходных исследований я узнал, что Hibernate 5.1.0 содержит новую функцию, которая позволяет вам создавать соединение, используя критерии нескольких столбцов/полей. Он называется cross-join
(см. Комментарии).
Смотрите этот билет и этот коммит.
Я спросил о Spring Data Gitter о том, когда это будет доступно для использования с Spring Boot 1.x. Они указали мне на установку свойства hibernate.version
в моем файле maven или gradle.
Следовательно, это работает, только если вы используете плагин org.springframework.boot
gradle.
apply plugin: "org.springframework.boot"
Поскольку я использую gradle, я в итоге добавил следующее к моему build.gradle
ext {
set('hibernate.version', '5.1.0.Final')
}
Или при использовании файла gradle.properties вы можете просто разместить эту строку там
hibernate.version=5.1.0.Final
И, наконец, я могу это сделать
Specification<S> specification = (root, query, cb) -> {
Join i18nJoin = root.join(collectionName, JoinType.LEFT);
Join i18nDefaultJoin = root.join(collectionName, JoinType.LEFT);
i18nJoin.on(cb.equal(i18nJoin.get("languageId"), 22));
i18nDefaultJoin.on(cb.equal(i18nDefaultJoin.get("languageId"), 1));
... where clause and return ...
}
В результате получается следующий запрос
SELECT *
FROM item i
LEFT JOIN i18n_item i18n ON i.item_id = i18n.item_id and i18n.language_id = 22
LEFT JOIN i18n_item i18n_default ON i.item_id = i18n_default.item_id i18n_default.language_id = 1
Обратите внимание, что использование метода on
не перезаписывает исходное предложение, заданное аннотацией ассоциации (в данном случае @OneToMany
), но расширяет его своими собственными критериями.