Do I ever need to explicitly flush GORM save calls in grails?
Asked Answered
D

5

42

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 modification 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
}
Duckling answered 9/6, 2011 at 6:40 Comment(0)
P
34

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()
  }
}
Palate answered 9/6, 2011 at 7:24 Comment(6)
Fair enough. The example I gave in my question was an idealised case of the problem. In reality each call to save() is ignorant of what queries will be used later... I don't want to always use (flush:true) out of fear, but what else can I do?Duckling
@Akusete:in your case, could you only call hibernate to flush session at the end?Runaway
This link may useful: grails.org/…Runaway
@Hoàng: My problem is that save() is called many times... each time it cannot be known at that instance if its the last time or not.Duckling
Is there any way to force flush to be called automatically when a db query is made that requires a hard db call?Duckling
@Akusete: then you can just call save() on all instances, and flush the hibernate session at the end. You can check grails.org/… for details.Runaway
W
60

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. without transaction without error

if the above same code has error:

without transaction with 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). with transaction without error

If the above code has an error: with transaction with error

I hope it helps, but if I made any error or missed to include big point , comment me I will update my pics.

Wordsmith answered 3/2, 2015 at 15:2 Comment(2)
Nice explanation!Mirthamirthful
This is great, nice graphics!Alisealisen
P
34

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()
  }
}
Palate answered 9/6, 2011 at 7:24 Comment(6)
Fair enough. The example I gave in my question was an idealised case of the problem. In reality each call to save() is ignorant of what queries will be used later... I don't want to always use (flush:true) out of fear, but what else can I do?Duckling
@Akusete:in your case, could you only call hibernate to flush session at the end?Runaway
This link may useful: grails.org/…Runaway
@Hoàng: My problem is that save() is called many times... each time it cannot be known at that instance if its the last time or not.Duckling
Is there any way to force flush to be called automatically when a db query is made that requires a hard db call?Duckling
@Akusete: then you can just call save() on all instances, and flush the hibernate session at the end. You can check grails.org/… for details.Runaway
A
9

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

Agricola answered 5/8, 2013 at 7:33 Comment(0)
I
1

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

Intrigante answered 14/5, 2015 at 18:17 Comment(0)
K
0

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.

Kilo answered 14/10, 2022 at 7:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.