web-dev-qa-db-ja.com

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   

最後の2つのクエリが異なる結果を返す理由を理解できません。

ただし、book.save(flush:true)を次のように変更すると、どちらのprintlnステートメントもすべての本を返します。

これは単一のアプリケーションでは必要ないという印象を受けました。

参考のために使用しています

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

@HoàngLong

私の問題を以下に示します。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
}

1つの解決策は、action1によって設定され、必要に応じてフラッシュするためにaction2によって使用されるフラグを持つことです。私の問題は、これは過度に複雑なソリューションであり、DB呼び出しの複雑さが増すにつれて拡張性が低下することです。

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
}
37
Akusete

あなたの場合、最初のステートメントはデータベースからデータを読み取るため、空のリストを返しますが、データはまだありません。

これがHibernateの仕組みです。(flush: true)でsaveを呼び出すと、Hibernateセッションがフラッシュされ、セッション内のすべてのデータがデータベースに永続化されます即時(flush:true)を使用しない場合、データはHibernateセッションにのみ記録され、Hibernateセッションがフラッシュされたときにのみデータベースに保持されます。セッションをフラッシュする時間は、パフォーマンスを最適化するためにHibernateによって自動的に決定されます。

一般に、データをすぐに永続化したくない場合を除いて、Hibernateに(最適化のために)作業を実行させる必要があります。

ピーター・レッドブルックによれば、

Hibernateにその仕事を任せ、手動でセッションをフラッシュする必要がある場合のみ、または少なくとも更新バッチの最後にのみセッションをフラッシュします。実際に使用する必要があるのは、データベースにあるはずのデータがデータベースに表示されていない場合だけです。私はそれが少し希望に満ちていることを知っていますが、そのようなアクションが必要な状況はデータベースの実装やその他の要因によって異なります。

From GORM Gotchas-part 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()
  }
}
33
Hoàng Long

GrailsでGORM save呼び出しを明示的にフラッシュする必要がありますか?

つまり、コードで行っているようにオブジェクトをすぐに使用したい場合はYes!です。

私は同じ問題に直面したので、これはいくつかの参考文献を読んだ後に得た写真です。

これはhibernateセッション問題です。
Hibernateセッションは、コントローラーアクションが呼び出されると作成され、アクションが戻る(またはエラーで早期に終了する)と終了します。コードがトランザクションコードを呼び出さない場合、Hibernateのdb対話は次のように表すことができます。
エントリーアクション名がactionNameで、アクションの呼び出しがエラーなしで完了すると仮定します。

[〜#〜] nb [〜#〜]トランザクションコードがないため、中央のバー(2次キャッシュは無効)。 without transaction without error

上記の同じコードにエラーがある場合:

without transaction with error

ただし、アクションがトランザクションメソッドを呼び出している場合、またはwithTransactionを使用してインライントランザクションを作成している場合(およびアクションの呼び出しがエラーなしで完了したと想定します)。 with transaction without error

上記のコードにエラーがある場合: with transaction with error

お役に立てば幸いですが、エラーが発生したり、大きなポイントが含まれていなかったりした場合は、コメントを書き込んで写真を更新してください。

52
dsharew

FlushModeの設定は何だったのでしょうか。

デフォルトでは "auto"に設定されており、DBに直接ヒットするすべてのクエリの前にセッションがフラッシュされることを意味します(他のケースも同様です)。その場合、Foo.findAllByBarが最初にセッションをフラッシュし(パフォーマンスの問題の可能性があります!)、DBから正しい値を読み取る必要があります。

FlushModeには他に2つの値があり、それらの1つを設定すると、問題が説明されます。最初は「manual」です。これは、セッションを手動でフラッシュすることを決定することを意味します(例:save(flush:true))。そうしないと、Foo.findAllByBarが古いDB状態を読み取ります。 2つ目は "commit"で、トランザクションがコミットされるたびにセッションがフラッシュされます。 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

8
Jacek

追加情報については、ドメインクラスイベント(afterUpdate、beforeUpdate、ect)でflushまたはsave(flush:true)を使用できません。スタックオーバーフローエラーが発生します。ただし、フラッシュせずにsave()を使用できます。

gorm docs

1
user2782001