3

我的问题很简单,但我找不到任何 GORM 语法。

考虑以下类:

class Article {
  String text

  static hasMany = [tags: String]

  static constraints= {
    tags(unique: true) //NOT WORKING
  }

}

我希望在我的约束中为每篇文章定义一个唯一的标签名称,但我无法使用上述语法。显然我需要在 DB 模式中类似:

create table article_tags (article_id bigint, tags_string varchar(255), unique (article_id , tags_string))

我怎样才能做到这一点?

PS:我还坚持设置标签最小和最大尺寸的限制

4

5 回答 5

5

仅供参考,您还可以在域类中使用自定义验证器:

    static constraints = {
    tags(validator: { 
        def valid = tags == tags.unique()
        if (!valid) errors.rejectValue(
            "tags", "i18n.message.code", "default message")
        return valid
    })

在数据库级别,您可以通过在grails-app/conf/hibernate/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>
于 2010-10-28T18:47:40.253 回答
2

最初我查看了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 的专家之一会提出更优雅的解决方案,但我在文档中找不到任何内容。

于 2010-10-28T15:41:28.737 回答
1

编辑:请注意,这种方法不考虑约束unique(article_id , tags_id)。它还引发了两个Articles 具有相同标签的问题。- 对不起。

虽然这没有正式记录(请参阅此处此处的 Grails 参考文档的相关部分)对一对多关联的约束被 GORM 简单地忽略了。这包括uniquenullable约束,可能还有。

这可以通过设置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, or 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
    }
}
于 2010-10-28T16:18:02.197 回答
0

我发现这样做的唯一方法是编写自定义约束并对重复进行数据库检查。我不认为有一种内置的方法可以使用 GORM 约束来实现这一点。

于 2010-10-28T18:35:55.973 回答
0

尝试这个:

http://johnrellis.blogspot.com/2009/09/grails-constraints-across-relationships.html

于 2010-10-28T17:39:23.100 回答