在我的服务代码中,我正在尝试创建或更新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:
我的应用程序在多个服务器上,因此同步块不起作用
答案 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 def someConcurrentSafeMethod()
不起作用 - 可能是因为服务包含在代理<强>已更新强>
由于应用程序部署在分布式系统上,上面不会解决这个问题(仍然可以帮助其他人)。经过我们对Slack的讨论,我只是总结了可能的方法:
request-handler-advice-chain
如果有人有更多想法,请分享。