【问题标题】:Grails null id error on constraints during integration test集成测试期间约束上的 Grails null id 错误
【发布时间】:2013-03-07 05:47:56
【问题描述】:

Grails 2.2.0

我正在尝试创建一个自定义约束来强制用户只有一个主电子邮件。这是导致错误的简化代码:

用户域类

class User {

    static hasMany = [emails: Email]

    static constraints = {
    }
}

电子邮件域类

class Email {

    static belongsTo = [user: User]
    String emailAddress
    Boolean isMaster

    static constraints = {

        emailAddress unique: ['user']
        isMaster validator: { val, obj ->
            return !val || Email.findByUserAndIsMaster(obj.user, true) == null
        }

    }
}

集成测试

class EmailTests {

    @Before
    void setUp() {

    }

    @After
    void tearDown() {
        // Tear down logic here
    }

    @Test
    void testSomething() {
        def john = (new User(login: 'johnDoe')).save(failOnError: true, flush: true)
        assert (new Email(emailAddress: 'john@gmail.com', user: john, isMaster: true)).save(failOnError: true)
    }
}

运行“grails test-app -integration”会导致:

|失败:testSomething(webapp.EmailTests)
| org.hibernate.AssertionFailure:webapp.Email 条目中的空 id(发生异常后不要刷新会话) 在 org.grails.datastore.gorm.GormStaticApi$_methodMissing_closure2.doCall(GormStaticApi.groovy:105) 在 webapp.Email$__clinit__closure1_closure2.doCall(Email.groovy:13) 在 org.grails.datastore.mapping.engine.event.AbstractPersistenceEventListener.onApplicationEvent(AbstractPersistenceEventListener.java:46) 在 webapp.EmailTests.testSomething(EmailTests.groovy:21)

如果我将唯一约束更改为自定义约束之后,问题将不会发生。这里发生了什么?我想了解这里的任何相关约束的顺序如何?

要明确,这不会导致问题:

static constraints = {
        isMaster validator: { val, obj ->
            return !val || Email.findByUserAndIsMaster(obj.user, true) == null
        }
        emailAddress unique: ['user']
    }

【问题讨论】:

  • 测试中的关系不是倒过来的吗?我想你想使用john.addToEmails()。约翰是拥有方。在您的测试中,您使用Email 就像它是拥有方:-)
  • 如果我这样做了同样的问题:void testSomething() { def john = new User(login: 'johnDoe').save(failOnError: true) def email = new Email(emailAddress: 'john@gmail.com', isMaster: true) john.addToEmails(email) assert john.save(failOnError: true) }

标签: hibernate grails constraints integration-testing grails-2.2


【解决方案1】:

我想我明白了……一对多的关系被打破了。

让我解释一下

  • 测试中的第一行创建了User john,该User john 被保存并刷新。
  • 第二行创建一个Email 并将john 设置为用户属性。

一旦你尝试保存Email 实例,GORM 就会报错。那是因为您将 john 分配给 Email,这是关系的反面。拥有方并没有意识到这一点,并且在这一点上什么也没有。简单的说。 在添加到用户之前,您无法保存实例并通过电子邮件发送实例。

这是一个应该有效的测试方法。

void testSomething() {
   def john = new User(login: 'johnDoe')

   john.addToEmails(new Email(emailAddress: 'john@gmail.com', isMaster: true))
   john.save(flush:true)

   assert false == john.errors.hasErrors()
   assert 1 == john.emails.size()  
}

addToEmails() 方法将电子邮件实例添加到集合中,并将用户设置为关系的反面。现在关系已经满足,保存 john 也应该保存所有电子邮件。


另一条路线

由于问题似乎是在 Email 验证器中对用户实例的引用,我认为也许您可以采取另一条路线。

class User {
    static hasOne = [master: Email]
    static hasMany = [emails: Email]
}

这将消除对有问题的验证器的需求,这使您的 Email 类依赖于 User 进行验证。您可以让用户对他拥有哪些电子邮件地址以及应该应用哪些规则负责。 您可以将验证器添加到 User 以验证您是否拥有电子邮件列表中不存在的主地址,并验证分配的所有地址是否都是唯一的。 比如:

static constraints = {
    master validator: { master, user, errors ->
        if (master.emailAddress in user.emails*.emailAddress) {
            errors.rejectValue('master', 'error.master', 'Master already in e-mails')
            return false
        }
    }

    emails validator: { emails, user, errors ->
        def addresses = emails*.emailAddress
        if (!addresses.equals(emails*.emailAddress.unique())) {
            errors.rejectValue('emails', 'error.emails', 'Non unique e-mail')
            return false
        }
    }
}

我做了一些测试,结果他们以这种方式做得很好。

【讨论】:

  • 有道理。但我不明白为什么我会收到“对象引用未保存的瞬态实例”错误,因为它是我尝试保存的第一个用户和电子邮件对,应该通过自定义约束。
  • 我认为Email.findByUserAndIsMaster(obj.user, true) 是问题所在。它引用了未保存的瞬态user。我认为您可能需要找到一种替代方法来强制执行您的约束。也许包装User.addToEmails 以解决这个“鸡和蛋”问题?
  • 好的。我希望将所有数据模型约束实现为域约束。看来我希望太大了。
  • 包装动态 addTo 方法到底是什么意思?覆盖用户中的方法并进行检查?之后如何调用原始的 addTo 方法?我也在想这可以通过使用域事件(例如 beforeInsert)来实现吗?
  • 别在意我说的关于包装的内容。这不计算 :-) 我用你可以采取的另一条路线更新了帖子。
猜你喜欢
  • 1970-01-01
  • 2011-06-02
  • 2011-04-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-07-18
相关资源
最近更新 更多