我是否需要在 grails 中显式刷新 GORM 保存调用?

发布于 2024-11-14 18:30:28 字数 1455 浏览 2 评论 0原文

我遇到了一个奇怪的情况,这似乎表明存在 GORM 缓存问题,

//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   

我无法理解为什么最后两个查询可能返回不同的结果。

但是,如果我对 book.save(flush:true) 进行以下修改。两个 println 语句都将返回所有书籍。

我的印象是在单个应用程序中没有必要这样做。

作为参考,我正在使用

  • DB:mysql
  • Groovy:1.7.10
  • Grails:1.3.7

@Hoàng Long

我的问题如下所示,假设 action1/action2 都被调用了很多次,没有特定的模式

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
}

一种解决方案是使用一个标志,该标志由 action1 设置并由 action2 在必要时用于刷新。我的问题是,这是一个过于复杂的解决方案,随着数据库调用复杂性的增加,它无法扩展。

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
}

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
}

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

眼角的笑意。 2024-11-21 18:30:28

我是否需要在 grails 中显式刷新 GORM 保存调用?

简而言之,如果您想像在代码中那样立即使用该对象,那么

我遇到了同样的问题,所以这是我阅读一些参考资料后得到的图片。

这是休眠会话问题
Hibernate 会话在调用控制器操作时创建,并在操作返回时结束(或因错误而提前终止)。如果代码没有调用任何事务代码,Hibernate 的数据库交互可以如下描述:
假设条目操作名称为 actionName 并且对该操作的调用完成且没有任何错误。

注意中间栏(二级缓存已禁用),因为没有任何事务代码。
without transaction without error

如果上述相同代码有错误:

without transaction with error

但是,如果您的操作正在调用事务方法或使用 withTransaction 创建内联事务(并假设对操作的调用已完成且没有任何错误)。
with transaction without error

如果上面的代码有错误:
with transaction with error

我希望它有帮助,但如果我犯了任何错误或错过了要点,请评论我会更新我的照片。

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.

如果没有 2024-11-21 18:30:28

在您的情况下,第一个语句返回空列表,因为它从数据库读取数据,但数据还不存在。

Hibernate 的工作原理如下:当您使用 (flush: true) 调用 save 时,它​​将刷新 Hibernate 会话,并立即将会话中的所有数据持久保存到数据库。如果不使用(flush:true),则数据仅记录在 Hibernate 会话中,并且仅当 Hibernate 会话刷新时才会保留在数据库中。刷新会话的时间由 Hibernate 自动确定以优化性能。

一般来说,您应该让 Hibernate 为您完成工作(为了优化)——除非您希望立即保留数据。

根据彼得·莱德布鲁克的说法:

让 Hibernate 完成它的工作,并且仅在您需要时手动刷新会话
必须,或者至少在最后
的一批更新。你应该只
如果你没有看到的话真的有用
数据库中应有的数据
那里。我知道这有点
软弱无力,但情况
何时需要采取此类行动取决于
关于数据库的实现和
其他因素。

来自 GORM 陷阱 - 第 1 部分

更新:明确如何刷新保存所有对象后的会话一次:

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()
  }
}

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()
  }
}
撩人痒 2024-11-21 18:30:28

我想知道你的 FlushMode 设置是什么。

默认情况下,它设置为“auto”,这意味着在每个直接命中数据库的查询之前刷新会话(也可能在其他情况下)。在这种情况下,您的 Foo.findAllByBar 应首先刷新会话(可能存在性能问题!)并从数据库读取正确的值。

FlushMode 还有另外两个值,如果您设置其中之一,那么它就会解释您的问题。
第一个是“手动”,这意味着您决定手动刷新会话(例如使用 save(flush:true))。如果您不这样做,那么 Foo.findAllByBar 将读取过时的数据库状态。
第二个是“commit”,这意味着会话会随着每次事务提交而刷新。如果您在 grails 中使用“withTransaction”语句,这会非常方便。

资源:
http://schneide.wordpress.com/2011/03/08 /the-grails-performance-switch-flush-modecommit/
http://docs.jboss.org/hibernate/entitymanager/3.5/参考/en/html/objectstate.html#d0e1215

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

2024-11-21 18:30:28

对于附加信息,您不能在域类事件(afterUpdate、beforeUpdate 等)中使用flush 或save(flush:true) ,这将导致堆栈溢出错误。
不过,您可以使用 save() 而无需刷新。

gorm 文档

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

静谧 2024-11-21 18:30:28

感谢 Jacek 提出了 flushMode,不过,Grails 3.3 对默认值进行了巨大更改:它是 AUTO,但现在是 COMMIT。这是出于性能原因,但可能会导致混乱和错误,因为:

  • 它是 Hibernate 默认值的更改,因此具有 Hibernate 经验的每个人都可能期望 findAll() 来刷新
  • 您现有的代码库可能依赖于此,您的集成测试可能无法捕获它,
  • 文档对此并不清楚,并且 GORM 7 文档似乎将其列为 AUTO,这是不正确的

您可以通过以下方式在 application.yml 中覆盖它:

hibernate:
  flush:
    mode: AUTO

我会补充说我看到一些文档声明您可以使用 grails.gorm.flushMode,但这对我不起作用。

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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文