使用Spring JPA规范和休眠加入不同的元素

2022-09-03 15:11:03

我正在尝试为I18N实现构建JPA规范,以便能够根据名称进行过滤。
在数据库中,我有多种语言的翻译。
问题是,大多数时候只指定了默认语言。
因此,当有人请求翻译非默认语言时,我希望它回退到默认语言。

到目前为止,我能够在规范中构建它

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)构建 with 子句。
有没有办法正确或以不同的方式实现它?

最后,我希望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个项目。当我使用名称进行筛选时,它将返回150个结果,当我翻转过滤器()时,它将返回类似30的结果。我预计他们加起来是200。like '%a%'not like '%a%'

这有效

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一起使用。他们指给我在我的 maven 或 gradle 文件中设置 hibernate.version 属性。因此,这似乎只当您使用gradle插件时才有效。org.springframework.boot

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


答案 2

推荐