I've already searched about this, but still cannot figure out what I'm doing wrong. After calling save()
the domain object id
is null
.
I've read it'll happen if there's a problem when saving the object, and that save(flush:true)
should throw an error if that's the case, but it's not. Look at my code and the output:
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"
Output:
no errors. New id: null
And when using 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"
Output:
no errors. New id: 17
As you can see, there aren't any errors creating the object, and I should be able to get the id
of the object after just calling save()
. Any ideas?
Thanks
You are misunderstanding the timing of when an object actually gets persisted to the database. An object does not get persisted when you call obj.save()
, it gets persisted when whichever of the following happens first:
A transaction can be started explicitly with
SomeDomainClass.withTransaction {
// code in here runs within a transaction
}
Normally, a transaction is also implicitly started for each call to a service method
class MyService {
void doSomething () {
// code in here runs within a transaction
}
}
If you don't explicitly or implicitly use transactions, saved objects get persisted when the Hibernate session closes, which is (roughly) when the HTTP request completes.
However, if you call someObject.save(flush: true)
you are telling Hibernate to persist the object immediately, which is why
album.addToPictures(pic).save(flush: true)
assigns an ID to the Picture
instance, but
album.addToPictures(pic).save()
will only assign the ID when the enclosing session/transaction is closed/committed
Futher to your comment
The problem is that I want to use the id as part of the name of a file I need to save. What about if I get an error saving the file? Should I use a explicit transaction and roll it back?
Yes, use an explicit transaction, and save the file once you're sure the object has been successfully persisted, roll the transaction back if persistence fails
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
Further to your comment
I don't want to save the file once I'm sure the object has been successfully persisted, but the opposite, I want to persist the object once the file has been successfully saved. So, I'm going to reformulate my questions as "Is there a way to configure Grails so that I can know the id that's going to be assigned to the new object before the object is effectively saved in the database?"
You already know that
album.addToPictures(pic).save(flush:true)
will provide you with the ID of the Picture
instance, so if you do this within a transaction you can get the ID without actually committing the transaction. However, I think this will only work if you're using a database that uses sequences (Oracle, Postgres). Something like the following should work
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
}
}
and that save(flush:true) should throw an error
That's not true. save(failOnError: true)
will cause an Exception to be thrown.
Nothing is wrong with your code, nor is anything wrong with the behavior you're seeing. By not calling flush; save()
, you're trying to access a generated ID before the actual insert happens. That is why it is null.
However, forcing a flush will (sometimes) force hibernate to write, which then gives you the ID you're expecting. If you need the ID immediately after calling save()
, use save(flush: true)
. There is nothing wrong with that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With