Grails save()尝试在更新

时间:2016-03-08 17:10:41

标签: java mysql hibernate grails gorm

在我的服务代码中,我正在尝试创建或更新Person域对象:

@Transactional
    def someServiceMethod(some params....) {
        try{
            def person = Person.findByEmail(nperson.email.toLowerCase())
            if (!person) {
                person = new Person()
                person.properties = nperson.properties

            } else {
                // update the person parameters (first/last name)
                person.firstName = nperson.firstName
                person.lastName = nperson.lastName
                person.phone = nperson.phone
            }

            if (person.validate()) {
                person.save(flush: true)
                //... rest of code
            }
            // rest of other code....
           } catch(e) {
                log.error("Unknown error: ${e.getMessage()}", e)
                e.printStackTrace()
                return(null)
    }

现在,在尝试使用现有电子邮件保存Person对象时,OCCASIONALLY上面的代码抛出以下异常:

Hibernate operation: could not execute statement; SQL [n/a]; Duplicate entry 'someemail@gmail.com' for key 'email_UNIQUE'; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'someemail@gmail.com' for key 'email_UNIQUE'

这很奇怪,因为我已经通过电子邮件找到了这个人,因此save()应该尝试更新记录而不是创建新记录。

我想知道为什么会发生这种情况!

编辑:

我在Grails 2.4.5和BuildConfig中的Hibernate插件是: runtime ':hibernate4:4.3.8.1'

EDIT2:

我的应用程序在多个服务器上,因此同步块不起作用

1 个答案:

答案 0 :(得分:3)

如果这是并发问题,这就是我们在这种情况下所做的事情。我们有很多并发后台进程可以在同一个表上运行。如果有这样的操作,它确实在同步块中,因此代码可能如下所示:

class SomeService {
    static transactional = false //service cannot be transactional

    private Object someLock = new Object() //synchronized block on some object must be used

    def someConcurrentSafeMethod(){
        synchronized(someLock){
            def person = Person.findByEmail(nperson.email.toLowerCase())
            ...
            person.save(flush: true) // flush is very important, must be done in synchronized block
        }
    }
}

使这项工作有一些重要的观点(根据我们的经验,而非官方的):

  • 服务不能是事务性的 - 如果服务是事务性的,则在方法返回值之后提交事务,并且方法内部的同步将不够。程序化交易可能是另一种方式
  • synchronized方法不够synchronized def someConcurrentSafeMethod()不起作用 - 可能是因为服务包含在代理
  • 会话必须在synchronized块内刷新
  • 将保存的每个对象都应该在synchronized块中读取,如果从外部方法传递它,则可能会遇到乐观锁定失败异常

<强>已更新

由于应用程序部署在分布式系统上,上面不会解决这个问题(仍然可以帮助其他人)。经过我们对Slack的讨论,我只是总结了可能的方法:

  • 对更新对象进行悲观锁定,并为插入锁定整个表(如果可能)
  • 将“危险的”数据库相关方法移动到单个服务器上,使用一些API(如REST)并从其他部署调用它(并使用上面的同步方法)
  • 使用多种保存方法 - 如果操作失败,请捕获异常并重试。这是由Spring Integration或Apache Camel等集成库支持的,是企业模式之一。有关Spring Integration的信息,请参阅request-handler-advice-chain
  • 使用某些东西来排队操作,例如JMS服务器

如果有人有更多想法,请分享。