web-dev-qa-db-fra.com

Dois-je jamais vider explicitement les appels de sauvegarde GORM dans les grails?

J'ai une situation étrange qui semble indiquer un problème de mise en cache 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   

Je ne comprends pas pourquoi les deux dernières requêtes peuvent renvoyer des résultats différents.

Cependant, si je fais la modification suivante de book.save (flush: true). Les deux instructions println renverront tous les livres.

J'avais l'impression que cela n'était pas nécessaire dans une seule demande.

Pour référence j'utilise

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

@ Hoàng Long

Mon problème est illustré ci-dessous, supposons que action1/action2 soient toutes deux appelées plusieurs fois, sans motif particulier

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
}

Une solution serait d'avoir un indicateur qui est défini par action1 et utilisé par action2 pour vider si nécessaire. Mon problème est qu'il s'agit d'une solution trop complexe, qui n'est pas évolutive à mesure que la complexité des appels DB augmente.

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

Dans votre cas, la première instruction renvoie une liste vide car elle lit les données de la base de données, mais les données ne sont pas encore là.

Voici comment fonctionne Hibernate: lorsque vous appelez save with (flush: true), il videra la session Hibernate, en conservant immédiatement toutes les données de la session dans la base de données . Si vous n'utilisez pas (flush:true), les données ne sont enregistrées que dans la session Hibernate et ne sont conservées dans la base de données que lorsque la session Hibernate est vidée . Le temps de vidage de la session est automatiquement déterminé par Hibernate pour optimiser les performances.

En règle générale, vous devez laisser Hibernate faire le travail pour vous (pour l'optimisation) - à moins que vous ne vouliez que les données soient conservées immédiatement.

Selon Peter Ledbrook:

Laissez Hibernate faire son travail et ne videz la session manuellement qu'en cas de besoin, ou du moins à la fin d'un lot de mises à jour. Vous ne devez vraiment l'utiliser que si vous ne voyez pas les données dans la base de données alors qu'elles devraient y être. Je sais que c'est un peu délirant, mais les circonstances où une telle action est nécessaire dépendent de la mise en œuvre de la base de données et d'autres facteurs.

De GORM Gotchas - partie 1

MISE À JOUR: pour être clair sur la façon de vider la session une fois après que tout l'objet a été enregistré:

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

Dois-je jamais vider explicitement les appels de sauvegarde GORM dans les grails?

En bref Oui !, si vous souhaitez utiliser l'objet immédiatement comme vous le faites dans votre code.

J'ai rencontré le même problème, c'est donc l'image que j'ai obtenue après avoir lu quelques références.

C'est session de mise en veille prolongée problème .
La session de mise en veille prolongée est créée lorsque l'action du contrôleur est appelée et se termine lorsque l'action revient (ou meurt avec une erreur plus tôt). Si un code n'appelle aucun code transactionnel, l'interaction db d'Hibernate peut être représentée comme suit:
Supposons que le nom de l'action d'entrée est actionName et l'appel à l'action se termine sans aucune erreur.

NB : La barre du milieu (le cache de 2e niveau est désactivé) car il n'y a aucun code transactionnel.  without transaction without error

si le même code ci-dessus a une erreur:

without transaction with error

Mais si votre action appelle une méthode transactionnelle ou crée une transaction en ligne avec withTransaction (et supposez que l'appel à l'action s'est terminé sans aucune erreur). with transaction without error

Si le code ci-dessus contient une erreur: with transaction with error

J'espère que cela aide, mais si j'ai fait une erreur ou manqué d'inclure un gros point, commentez-moi, je mettrai à jour mes photos.

52
dsharew

Je me demande quel était votre réglage FlushMode.

Par défaut, il est défini sur " auto " et cela signifie que la session est vidée avant chaque requête qui frappe directement DB (et probablement dans d'autres cas également). Dans ce cas, votre Foo.findAllByBar doit d'abord vider la session (problème de performances possible!) Et lire la valeur correcte dans la base de données.

Il existe deux autres valeurs pour FlushMode et si vous en définissez une, cela expliquerait vos problèmes. Le premier est " manual ", ce qui signifie que vous décidez de vider la session manuellement (par exemple avec save (flush: true)). Si vous ne le faites pas, alors Foo.findAllByBar lit l'état de la base de données obsolète. Le second est " commit " ce qui signifie que la session est vidée à chaque validation de transaction. C'est assez pratique si vous utilisez l'instruction " withTransaction " dans Grails.

Ressources: 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

Pour plus d'informations, vous ne pouvez pas utiliser flush ou save (flush: true) dans vos événements de classe de domaine (afterUpdate, beforeUpdate, ect). Cela entraînera une erreur de dépassement de pile. Vous pouvez cependant utiliser save () sans vidage.

documents Gorm

1
user2782001