java - Grails save() 尝试在应该更新时创建新对象

标签 java mysql hibernate grails grails-orm

在我的服务代码中,我试图创建或更新一个 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 对象时抛出以下异常:

Hibernate 操作:无法执行语句; SQL [不适用];键“email_UNIQUE”的重复条目“someemail@gmail.com”;嵌套的异常是 com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Duplicate entry 'someemail@gmail.com' for key 'email_UNIQUE'

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

我想知道为什么会这样!

编辑:

我在 grails 2.4.5 上,BuildConfig 中的 Hibernate 插件是: 运行时':hibernate4:4.3.8.1'

编辑2:

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

最佳答案

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

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 def someConcurrentSafeMethod() 将不起作用 - 可能是因为服务被包装在代理中
  • session 必须在同步块(synchronized block)内刷新
  • 每个要保存的对象,都应该在同步块(synchronized block)中读取,如果你从外部方法传递它,你可能会遇到乐观锁定失败的异常

已更新

由于应用部署在分布式系统上,以上并不能解决这里的问题(可能对其他人有帮助)。在我们对 Slack 进行讨论之后,我总结了可能的方法:

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

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

关于java - Grails save() 尝试在应该更新时创建新对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35873657/

相关文章:

java - init 方法调用失败;添加 Hibernate envers 后嵌套异常是 java.lang.NoSuchMethodError

java - 关闭组件内字段的乐观锁

java - 禁用上下文 LOB 创建,因为 JDBC 驱动程序报告 JDBC 版本 [3] 小于 4

java - Android中Togglebutton结合Intent显示Image

mysql - 相当于 MySql 的 db2 REPLACE INTO

MySQL:如何显示没有特定值的行

MySQL 没有在 Ubuntu 16.04 上获取 5.7.13 中的符号链接(symbolic link) cnf 更改

java - 从 Java 7 转换为 6,Matcher.group

Java注释处理-如何处理已经处理过的代码?

java - Java中的UploadFileAsync?