我是否需要在 grails 中显式刷新 GORM 保存调用?
我遇到了一个奇怪的情况,这似乎表明存在 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
简而言之,如果您想像在代码中那样立即使用该对象,那么是!
我遇到了同样的问题,所以这是我阅读一些参考资料后得到的图片。
这是休眠会话问题。
Hibernate 会话在调用控制器操作时创建,并在操作返回时结束(或因错误而提前终止)。如果代码没有调用任何事务代码,Hibernate 的数据库交互可以如下描述:
假设条目操作名称为 actionName 并且对该操作的调用完成且没有任何错误。
注意:中间栏(二级缓存已禁用),因为没有任何事务代码。
如果上述相同代码有错误:
但是,如果您的操作正在调用事务方法或使用 withTransaction 创建内联事务(并假设对操作的调用已完成且没有任何错误)。
如果上面的代码有错误:
我希望它有帮助,但如果我犯了任何错误或错过了要点,请评论我会更新我的照片。
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.
在您的情况下,第一个语句返回空列表,因为它从数据库读取数据,但数据还不存在。
Hibernate 的工作原理如下:当您使用
(flush: true)
调用 save 时,它将刷新 Hibernate 会话,并立即将会话中的所有数据持久保存到数据库。如果不使用(flush:true)
,则数据仅记录在 Hibernate 会话中,并且仅当 Hibernate 会话刷新时才会保留在数据库中。刷新会话的时间由 Hibernate 自动确定以优化性能。一般来说,您应该让 Hibernate 为您完成工作(为了优化)——除非您希望立即保留数据。
根据彼得·莱德布鲁克的说法:
来自 GORM 陷阱 - 第 1 部分
更新:明确如何刷新保存所有对象后的会话一次:
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:
From GORM Gotchas - part 1
UPDATE: to be clear about how to flush the session one time after all the object get saved:
我想知道你的 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
对于附加信息,您不能在域类事件(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
感谢 Jacek 提出了
flushMode
,不过,Grails 3.3 对默认值进行了巨大更改:它是 AUTO,但现在是 COMMIT。这是出于性能原因,但可能会导致混乱和错误,因为:findAll()
来刷新您可以通过以下方式在 application.yml 中覆盖它:
我会补充说我看到一些文档声明您可以使用 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:findAll()
to cause a flushYou can override this in application.yml via:
I'll add that I saw some documentation claim you can use grails.gorm.flushMode, but that did not work for me.