40

我有一个奇怪的情况,这似乎表明存在 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 语句都将返回所有书籍。

我的印象是,这在单个应用程序中是不必要的。

供参考,我正在使用

  • 数据库:mysql
  • 时髦的:1.7.10
  • 圣杯: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
}
4

4 回答 4

57

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

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

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

这是休眠会话问题
休眠会话在调用控制器操作时创建,并在操作返回时结束(或提前因错误而死)。如果代码没有调用任何事务代码,Hibernate 的 db 交互可以这样描述:
假设入口动作名称是actionName,并且对动作的调用完成且没有任何错误。

注意中间条(二级缓存被禁用)因为没有任何事务代码。 无交易无错误

如果上面相同的代码有错误:

没有错误的交易

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

如果上面的代码有错误: 有错误的交易

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

于 2015-02-03T15:02:24.303 回答
34

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

这就是 Hibernate 的工作原理:当您调用 save with 时(flush: true),它将刷新 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()
  }
}
于 2011-06-09T07:24:56.730 回答
8

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

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

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

资源: 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

于 2013-08-05T07:33:32.783 回答
1

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

戈姆文档

于 2015-05-14T18:17:46.780 回答