使用 Spring JPA 规范和 Hibernate 加入不同的元素
Join on different elements using Spring JPA Specifications and Hibernate
我正在尝试为 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 个项目。当我使用 like '%a%'
过滤名称时,它会 return 150 个结果,当我翻转过滤器 (not like '%a%'
) 时,它会 return 大约 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%'))
)
这不起作用(return 元素数量不正确)
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 规范来实现查询?
经过一个周末的研究,我发现 Hibernate 5.1.0 包含一个新功能,允许您使用多个 columns/fields 的条件构建连接。它被称为 cross-join
(见评论)。
参见this ticket and this commit。
我在 Spring Data Gitter 上询问了何时可以与 Spring Boot 1.x 一起使用。他们指示我设置 hibernate.version
property in my 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
),而是使用您自己的条件对其进行扩展。
我正在尝试为 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 个项目。当我使用 like '%a%'
过滤名称时,它会 return 150 个结果,当我翻转过滤器 (not like '%a%'
) 时,它会 return 大约 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%'))
)
这不起作用(return 元素数量不正确)
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 规范来实现查询?
经过一个周末的研究,我发现 Hibernate 5.1.0 包含一个新功能,允许您使用多个 columns/fields 的条件构建连接。它被称为 cross-join
(见评论)。
参见this ticket and this commit。
我在 Spring Data Gitter 上询问了何时可以与 Spring Boot 1.x 一起使用。他们指示我设置 hibernate.version
property in my 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
),而是使用您自己的条件对其进行扩展。