web-dev-qa-db-ja.com

SpringJPA仕様と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句を使用してこれを解決する


たとえば、ユーザーが英語向けのアイテムに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仕様を使用して機能するクエリを実装する方法はありますか?

15
Robin Hermans

週末の調査の結果、Hibernate 5.1.0には、複数の列/フィールドの基準を使用して結合を構築できる新機能が含まれていることがわかりました。それはcross-joinと呼ばれます(コメントを参照)。

このチケット および このコミット を参照してください。

Spring Data Gitterで、Spring Boot1.xでいつ使用できるようになるかを尋ねました。彼らは私に hibernate.versionプロパティを私のmaven またはgradleファイルに設定するように指示しました。したがって、これはorg.springframework.bootgradleプラグインを使用する場合にのみ機能するようです。

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)によって設定された元の句は上書きされませんが、独自の基準で拡張されることに注意してください。

10
Robin Hermans