Do I ever need to explicitly flush GORM save calls in grails?
I have a strange situation which appears to indicate a GORM cacheing problem
//begin with all book.status's as UNREAD
Book.list().each { book.status = Status.READ ; book.save() }
println (Book.findAllByStatus (Status.READ)) //will print an empty list
println (Book.list().findAll (it.status == Status.READ)) // will print all books
I cannot understand why the last two queries could return different results.
However if I make the following mo开发者_如何学Godification of book.save(flush:true). Both of the println statements will return all books.
I was under the impression that this was not necessary within a single application.
For reference I'm using
- DB: mysql
- Groovy: 1.7.10
- Grails: 1.3.7
@Hoàng Long
My problem is demonstrated below, suppose action1/action2 are both called many many times, in no particular pattern
def action1 = {
Foo foo = Foo.get(params.id)
//... modify foo
foo.save() //if I flush here, it will be inefficient if action1 is called in sequence
}
def action2 = {
//if I flush here, it will be inefficient if action2 is called in sequence
List<Foo> foos = Foo.findAllByBar (params.bar)
//... do something with foos
}
One solution would be to have a flag which is is set by action1 and used by action2 to flush if necessary. My issue is that this is an overly complex solution, which is not scalable as the complexity of DB calls increases.
boolean isFlushed = true
def action1 = {
Foo foo = Foo.get(params.id)
//... modify foo
foo.save()
isFlushed = false
}
def action2 = {
if (!isFlushed) {
//flush hibernate session here
}
List<Foo> foos = Foo.findAllByBar (params.bar)
//... do something with foos
}
Do I ever need to explicitly flush GORM save calls in grails?
In short Yes!, if you want to use the object immediately as you are doing in your code.
I faced same problem, so this is the picture I got after reading some refs.
This is hibernate session issue.
Hibernate session is created when controller action is called and ends when the action returns ( or dies with error early). If a code is not calling any transactional code Hibernate's db interaction can be depicted like this:
Assume the entry action name is actionName and call to the action completes without any error.
NB:The middle bar ( 2nd level cache is disabled) because there is no any transactional code.
if the above same code has error:
But if your action is calling transactional method or is creating inline transaction with withTransaction ( and assume the call to the action completed without any error).
If the above code has an error:
I hope it helps, but if I made any error or missed to include big point , comment me I will update my pics.
In your case, the first statement return empty list because it reads data from the database, but the data isn't there yet.
It's how Hibernate works: When you call save with (flush: true)
, it will flush the Hibernate session, persistent all data in session to database immediately. If not using (flush:true)
, the data is only recorded in Hibernate session and only get persisted in database when Hibernate session is flushed. The time to flush the session is automatically determined by Hibernate to optimize the performance.
Generally, you should let Hibernate do the work for you (for optimization sake) - unless you want the data are persisted right away.
According to Peter Ledbrook:
Let Hibernate do it's job and only manually flush the session when you have to, or at least only at the end of a batch of updates. You should only really use if you're not seeing the data in the database when it should be there. I know that's a bit wishy-washy, but the circumstances when such action is necessary depend on the database implementation and other factors.
From GORM Gotchas - part 1
UPDATE: to be clear about how to flush the session one time after all the object get saved:
import org.hibernate.*
class SomeController {
SessionFactory sessionFactory
def save = {
assert sessionFactory != null
// loop and save your books here
def hibSession = sessionFactory.getCurrentSession()
assert hibSession != null
hibSession.flush()
}
}
I wonder what was your FlushMode setting.
By default it is set to "auto" and it means that session is flushed before every query which hits DB directly (and probably in other cases too). In that case your Foo.findAllByBar should flush the session first (possible performance issue!) and read correct value from the DB.
There are two other values for FlushMode and if you set one of them then it would explain your problems. First is "manual" which means you decide to manual flush session (e.g. with save(flush:true)). If you don't do that then Foo.findAllByBar reads outdated DB state. Second one is "commit" which means that session is flushed with every transaction commit. That is quite handy if you use "withTransaction" statement in grails.
Resources: http://schneide.wordpress.com/2011/03/08/the-grails-performance-switch-flush-modecommit/ http://docs.jboss.org/hibernate/entitymanager/3.5/reference/en/html/objectstate.html#d0e1215
For added info, you can't use flush or save(flush:true) in your domain class events (afterUpdate, beforeUpdate, ect) It will cause a stack overflow error. You can use save() without flush though.
gorm docs
Credit to Jacek for bringing up flushMode
, however, Grails 3.3 made a huge change to the default: it was AUTO, but is now COMMIT. This is for performance reasons, but can lead to confusion and bugs because:
- it is a change from the Hibernate default, so everyone with Hibernate experience might expect a
findAll()
to cause a flush - your existing codebase may have relied on this and your integration tests may not catch it
- documentation is not clear on this, and the GORM 7 docs appear to list it as AUTO, which is incorrect
You can override this in application.yml via:
hibernate:
flush:
mode: AUTO
I'll add that I saw some documentation claim you can use grails.gorm.flushMode, but that did not work for me.
精彩评论