I have a GORM object that I'm working with in an integration test. It has a beforeUpdate
hook that keeps a history of the previous password hashes. The code looks something like this:
class Credentials {
List passwordHistory = []
String username
String password
static hasMany = [passwordHistory : String]
def beforeUpdate() {
// of course I'm not really storing plain-text passwords, but
// this is just for illustration.
if (isDirty('password')) { passwordHistory << password }
}
}
In the integration test, I'm wondering why:
appUser.credentials.password = newPassword
appUser.save(flush: true)
sessionFactory.currentSession.flush()
AppUser.withNewSession {
appUser = AppUser.get(appUser.id)
appUser.credentials.empty // eagerly fetch the list while session is open
}
assert !appUser.credentials.passwordHistory.empty() // contains the previous password
works, but
appUser.credentials.password = newPassword
appUser.save()
sessionFactory.currentSession.flush()
AppUser.withNewSession {
appUser = AppUser.get(appUser.id)
appUser.credentials.empty // eagerly fetch the list while session is open
}
assert !appUser.credentials.passwordHistory.empty() // is empty
does not. The difference is the flush: true
in the appUser.save()
call. I thought the call to save()
attached the object to the current session, but flushing the current session does not add the password to the passwordHistory
list. What's really going on here?
Flushing the session forces Hibernate to synchronize the in-memory state of the Session with the database (i.e. to write changes to the database). By default, Hibernate will flush changes automatically for you: before some query executions. when a transaction is committed.
Manual, the programmer is informing hibernate that he/she will handle when to pass the data to the database. Under this configuration the session. flush() call will save the object instances to the database. A session. clear() call acutally can be used to clear the persistance context.
flush (optional) - When set to true flushes the persistence context, persisting the object immediately and updating the version column for optimistic locking.
As said in org. hibernate. Session docs, Must be called at the end of a unit of work, before commiting the transaction and closing the session (depending on flush-mode, Transaction. commit() calls this method).
If I'm interpreting the Grails code correctly, you're actually dealing with two different sessions. From the documentation:
Integration tests run inside a database transaction by default, which is rolled back at the end of the each test. This means that data saved during a test is not persisted to the database.
If you dig through the Grails GORM method logic, you'll see that when you're within a transaction, GORM grabs its session from a ThreadLocal
resources map that's maintained by the TransactionSynchronizationManager
class. If it doesn't find one, it opens a new session and binds it to the map - important distinction, it explicitly opens a new session. It doesn't just call sessionFactory.getCurrentSession()
.
At the end of the save()
GORM logic, if you pass in flush:true
it will flush the session associated with the transaction - the one it got from the resources map in TransactionSynchronizationManager
.
On the other hand when you call flush()
you're calling it on the session that you get from sessionFactory.getCurrentSession()
which I believe is a session bound to your thread from the CurrentSessionContext
used by the Hibernate SessionFactory
. The actual implementation of CurrentSessionContext
is beside the point because (unless there's a Grails-specific implementation that I'm missing) it wouldn't return the same session that's held by the TransactionSynchronizationManager
.
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