1 対多の関連付けに対して DB レベルで一意性を設定するにはどうすればよいですか?
-
27-09-2019 - |
質問
私の問題は単純ですが、これに対応する GORM 構文が見つかりませんでした。
次のクラスについて考えてみましょう。
class Article {
String text
static hasMany = [tags: String]
static constraints= {
tags(unique: true) //NOT WORKING
}
}
欲しいです 記事ごとに 1 つの一意のタグ名 制約で定義されていますが、上記の構文では作成できません。明らかに、DB スキーマには次のようなものが必要です。
create table article_tags (article_id bigint, tags_string varchar(255), unique (article_id , tags_string))
どうやってやるの?
追伸:また、タグの最小サイズと最大サイズの制約を設定するのにも行き詰まっています
解決
FYI、あなたはまた、ドメインクラスにカスタムバリデータを使用することができます:
static constraints = {
tags(validator: {
def valid = tags == tags.unique()
if (!valid) errors.rejectValue(
"tags", "i18n.message.code", "default message")
return valid
})
データベース・レベルでは、することができます<のhref = "http://docs.jboss.org/hibernate/core/3.3/reference/en/html/mapping.html#mapping-database-object" のrel =」
:noreferrerは "> のGrailsのアプリ/ confに/休止状態/ hibernate.cfg.xmlのの中で次のコードを持つことにより、DDL生成をカスタマイズ<hibernate-mapping>
<database-object>
<create>
ALTER TABLE article_tags
ADD CONSTRAINT article_tags_unique_constraint
UNIQUE(article_id, tags_string);
</create>
<drop>
ALTER TABLE article_tags
DROP CONSTRAINT article_tags_unique_constraint;
</drop>
</database-object>
</hibernate-mapping>
他のヒント
最初に私が見たのは、 joinTable
マッピングをサポートするかどうかを確認する unique
キーを押しますが、そうではありません。
私が考える最善の解決策は、次の組み合わせです。
SQL ステートメントを手動で実行して、一意制約を追加します。何らかのデータベース管理ツール (例:Liquibase)、それは理想的な場所でしょう。
アソシエーションを明示的に宣言します。
Set
. 。いずれにせよ、これにより Hibernate が一意の制約に遭遇するのを避けることができます。class Article { static hasMany = [tags: String] Set<String> tags = new HashSet<String>() }
別の解決策は、子ドメインを明示的に宣言することです (Tag
) を追加して多対多の関係を設定します。 unique
そこにある結合テーブルへのキーを使用して constraints
. 。しかし、それもあまり良い解決策ではありません。以下に原始的な例を示します。
class Article {
static hasMany = [articleTags: ArticleTag]
}
class Tag {
static hasMany = [articleTags: ArticleTag]
}
class ArticleTag {
Article article
Tag tag
static constraints = {
tag(unique: article)
}
}
ただし、この場合、コード内で多対多の関係を明示的に管理する必要があります。少し不便ですが、関係全体を完全に制御できます。本質的な部分を知ることができます 詳細はこちら ( Membership
リンクされた例のクラスは、 ArticleTag
私の中で)。
おそらく、GORM に精通した達人の 1 人が、より洗練されたソリューションを提案してくれるでしょう。しかし、ドキュメントには何も見つかりません。
編集:このアプローチでは次のことが行われることに注意してください。 ない を検討してください unique(article_id , tags_id)
制約。それはまた、2つの問題を引き起こします Article
同じタグが付いています。- ごめん。
これは正式に文書化されていませんが (Grails リファレンス ドキュメントの関連部分を参照してください) ここ そして ここ) 1 対多の関連付けに関する制約は、GORM によって単に無視されます。これも unique
そして nullable
制約があり、おそらくあらゆる制約があります。
これは設定によって証明できます dbCreate="create"
次に、データベース スキーマ定義を確認します。あなたのための Article
サンプルと PostgreSQL データベースの場合、これは次のようになります。
CREATE TABLE article_tags
(
article_id bigint NOT NULL,
tags_string character varying(255),
CONSTRAINT fkd626473e45ef9ffb FOREIGN KEY (article_id)
REFERENCES article (id) MATCH SIMPLE
ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT article0_tags_article0_id_key UNIQUE (article_id)
)
WITH (
OIDS=FALSE
);
上でわかるように、制約はありません。 tags_string
カラム。
関連フィールドに対する制約とは対照的に、ドメイン クラスの「通常の」インスタンス フィールドに対する制約は期待どおりに機能します。
したがって、ある種の Tag
, 、 または TagHolder
, 、ドメイン クラスを提供するパターンを見つける必要があります。 Article
とともに クリーンなパブリック API.
まず最初にご紹介するのは、 TagHolder
ドメインクラス:
class TagHolder {
String tag
static constraints = {
tag(unique:true, nullable:false,
blank:false, size:2..255)
}
}
そしてそれを Article
クラス:
class Article {
String text
static hasMany = [tagHolders: TagHolder]
}
クリーンなパブリック API を提供するために、メソッドを追加しています。 String[] getTags()
, void setTags(String[]
. 。そうすることで、次のように名前付きパラメーターを使用してコンストラクターを呼び出すこともできます。 new Article(text: "text", tags: ["foo", "bar"])
. 。も追加しています。 addToTags(String)
クロージャ。GORM の対応する「マジック メソッド」を模倣します。
class Article {
String text
static hasMany = [tagHolders: TagHolder]
String[] getTags() {
tagHolders*.tag
}
void setTags(String[] tags) {
tagHolders = tags.collect { new TagHolder(tag: it) }
}
{
this.metaClass.addToTags = { String tag ->
tagHolders = tagHolders ?: []
tagHolders << new TagHolder(tag: tag)
}
}
}
これは回避策ですが、それほど多くのコーディングは必要ありません。
欠点としては、追加の JOIN テーブルが必要になることです。それにもかかわらず、このパターンでは、利用可能な制約を適用できます。
最終的に、テスト ケースは次のようになります。
class ArticleTests extends GroovyTestCase {
void testUniqueTags_ShouldFail() {
shouldFail {
def tags = ["foo", "foo"] // tags not unique
def article = new Article(text: "text", tags: tags)
assert ! article.validate()
article.save()
}
}
void testUniqueTags() {
def tags = ["foo", "bar"]
def article = new Article(text: "text", tags: tags)
assert article.validate()
article.save()
assert article.tags.size() == 2
assert TagHolder.list().size() == 2
}
void testTagSize_ShouldFail() {
shouldFail {
def tags = ["f", "b"] // tags too small
def article = new Article(text: "text", tags: tags)
assert ! article.validate()
article.save()
}
}
void testTagSize() {
def tags = ["foo", "bar"]
def article = new Article(text: "text", tags: tags)
assert article.validate()
article.save()
assert article.tags.size() == 2
assert TagHolder.list().size() == 2
}
void testAddTo() {
def article = new Article(text: "text")
article.addToTags("foo")
article.addToTags("bar")
assert article.validate()
article.save()
assert article.tags.size() == 2
assert TagHolder.list().size() == 2
}
}
私はこれを行うために見つけた唯一の方法は、カスタム制約を作成し、複製用のデータベースのチェックを行うことです。私はそこには、内蔵されている方法、これを達成するためにGORMの制約を使用しないと思います。