Joe
Joe

Reputation: 4625

How is flush: true different from manually flushing the currentSession?

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?

Upvotes: 7

Views: 969

Answers (2)

Giuseppe Iacobucci
Giuseppe Iacobucci

Reputation: 423

In the first case you flush the session, so the object become "dirty". When you open a new session with newSession closure, you can modify an object, but if you want to make the modification "dirty" you must do an explicit call to the save() method in the new session open. If you do that, after closure closed, you can see the modification you made at the object in the closure.

In the second case before the withNewSession the object isn't dirty, so non explicit call is nedeed for modifying the object, and this is the reason why you see the list empty in the second case.

Upvotes: 0

chrislatimer
chrislatimer

Reputation: 3560

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.

Upvotes: 1

Related Questions