圣杯。调用保存后id为空

Posted

技术标签:

【中文标题】圣杯。调用保存后id为空【英文标题】:Grails. Id is null after calling save 【发布时间】:2012-11-28 00:39:47 【问题描述】:

我已经对此进行了搜索,但仍然无法弄清楚我做错了什么。调用save() 后,域对象idnull

我已经读过,如果保存对象时出现问题,就会发生这种情况,如果是这种情况,save(flush:true) 应该会抛出错误,但事实并非如此。看看我的代码和输出:

def pic = new Picture(title:'XX', path:"XXX")
album.addToPictures(pic).save()
if(pic.validate())
   println "no errors. New id: " + pic.id
else
   println "with errors"

输出:

no errors. New id: null

当使用flush:true时

def pic = new Picture(title:'XX', path:"XXX")
album.addToPictures(pic).save(flush:true)
if(pic.validate())
   println "no errors. New id: " + pic.id
else
   println "with errors"

输出:

no errors. New id: 17

如您所见,创建对象没有任何错误,我应该能够在调用save() 后获取对象的id。有什么想法吗?

谢谢

【问题讨论】:

【参考方案1】:

您误解了对象实际持久化到数据库的时间。当您调用 obj.save() 时,对象不会被持久化,它会在以下任何一个先发生时被持久化:

调用 save() 的事务已提交 调用 save() 的 Hibernate 会话已关闭

事务可以通过

显式启动
SomeDomainClass.withTransaction 
  // code in here runs within a transaction

通常,每次调用服务方法时也会隐式启动事务

class MyService 

  void doSomething () 
    // code in here runs within a transaction
    

如果您不显式或隐式使用事务,则保存的对象会在 Hibernate 会话关闭时(大致)在 HTTP 请求完成时持久化。

但是,如果您调用 someObject.save(flush: true),您是在告诉 Hibernate 立即持久化对象,这就是为什么

album.addToPictures(pic).save(flush: true)

Picture 实例分配一个ID,但是

album.addToPictures(pic).save()

仅在封闭会话/事务关闭/提交时分配 ID

更新

继续您的评论

问题是我想使用 id 作为我需要保存的文件名的一部分。如果我在保存文件时遇到错误怎么办?我应该使用显式事务并将其回滚吗?

是的,使用显式事务,并在确定对象已成功持久化后保存文件,如果持久化失败则回滚事务

def pic = new Picture(title:'XX', path:"XXX")

Picture.withTransaction  TransactionStatus status ->        

  try 
    album.addToPictures(pic).save()

   catch(ex) 
    status.setRollbackOnly()
    throw ex
  


// At this point you can be sure pic has been persisted, so use pic.id to save the file

更新 2

进一步的评论

我不想在确定对象已成功保存后保存文件,但相反,我想在文件成功保存后保存对象。所以,我将把我的问题重新表述为“有没有办法配置 Grails,以便在对象有效保存到数据库中之前知道要分配给新对象的 id?”

你已经知道了

album.addToPictures(pic).save(flush:true)

将为您提供Picture 实例的 ID,因此如果您在事务中执行此操作,则无需实际提交事务即可获得 ID。但是,我认为这仅在您使用使用序列的数据库(Oracle,Postgres)时才有效。像下面这样的东西应该可以工作

Picture.withTransaction  TransactionStatus status ->        

  try 
    def pic = new Picture(title:'XX', path:"XXX")  
    album.addToPictures(pic).save(flush: true)

    // pic.id should now be assigned, so save the file. I'm assuming an
    // an exception will be thrown if saving the file fails

   catch(ex) 
    // you may also want to try rolling back the file save here, i.e. delete it
    status.setRollbackOnly()
    throw ex
  

【讨论】:

嗨。一旦我确定对象已成功保存,我不想保存文件,但相反,我想在文件成功保存后保存对象。所以,我将把我的问题重新表述为“有没有办法配置 Grails,以便在对象有效保存在数据库中之前,我可以知道将分配给新对象的 id?”自从我不使用 Hibernate 以来已经有很长时间了,但我记得在提交事务之前能够获取新的要保存对象的 id。谢谢【参考方案2】:

并且 save(flush:true) 应该抛出一个错误

那不是真的。 save(failOnError: true) 将导致抛出异常。

您的代码没有问题,您看到的行为也没有任何问题。通过不调用flush; save(),您正试图在实际插入发生之前访问生成的 ID。这就是它为空的原因。

但是,强制刷新将(有时)强制休眠写入,然后为您提供您期望的 ID。如果您在拨打save() 后立即需要该ID,请使用save(flush: true)。这没有什么问题。

【讨论】:

感谢您的回答。问题是我想使用 id 作为我需要保存的文件名的一部分。如果我在保存文件时遇到错误怎么办?我应该使用显式事务并将其回滚吗?

以上是关于圣杯。调用保存后id为空的主要内容,如果未能解决你的问题,请参考以下文章

linux字符测试

ParseInstallation 保存后解析 deviceToken 为空

双向实体关系使用Spring将id保存为空

CakePHP - 当某些字段为空时,防止模型数据保存在 saveMany 调用中

使用脚本选项及组合条件测试

使用 Mongoose 和 GraphQL 保存实体后数据为空