2014-02-16 7 views
10

Wenn ich brauche eine Liste von Objekten zu speichern, und jedes Objekt sollte in einer eigenen Transaktion gespeichert werden (so dass, wenn man sie nicht nicht alle gescheitert), ich mache es wie folgt aus:Umgang mit Grails Transaktionen programmatisch

List<Book> books = createSomeBooks() 
books.each { book -> 
    Book.withNewSession { 
    Book.withTransaction {TransactionStatus status -> 
     try { 
     book.save(failOnError: true) 
     } catch (ex) { 
     status.setRollbackOnly() 
     } 
    } 
    } 
} 

Ich benutze Book.withNewSession, denn wenn ein Buch nicht speichern und die Transaktion zurückgesetzt wird, wird die Sitzung ungültig, die nachfolgende Bücher speichern zu verhindern. Es gibt jedoch ein paar Probleme mit diesem Ansatz:

  1. Es ist ein bisschen ausführliche
  2. Eine neue Sitzung wird immer für jedes Buch erstellt werden, auch wenn das vorherige Buch

gelungen Gibt es ein besserer Weg? Eine Möglichkeit, die mir aufgetreten ist abhängigkeits zu injizieren, um die Hibernate SessionFactory und tun dies stattdessen

List<Book> books = createSomeBooks() 
books.each { book -> 
    try { 
    Book.withTransaction { 
     book.save(failOnError: true) 
    } 
    } catch (ex) { 
    // use the sessionFactory to create a new session, but how....? 
    } 
} 
+0

Warum sollten Sie für jede Iteration eine neue Sitzung erstellen? IMHO reicht aus, um nur mit Transaction Block in jeder Iteration auszuführen, aber alles kann in einer Sitzung passieren. – lukelazarovic

+0

@lukelazarovic Ich brauche nur eine neue Sitzung, wenn ein Rollback auftritt. Das Problem, das ich habe, ist, dass ich nicht weiß, wie ich eine neue Sitzung im Catch-Block erstellen kann –

Antwort

10

Dies sollte es tun:

List<Book> books = createSomeBooks() 
books.each { book -> 
    Book.withNewTransaction {TransactionStatus status -> 
    try { 
     book.save(failOnError: true) 
    } catch (ex) { 
     status.setRollbackOnly() 
    } 
    } 
} 

Die Sitzung nicht ungültig ist, wenn Sie Rollback, Es ist gerade gelöscht. Jegliche Versuche, auf Entitäten zuzugreifen, die aus der DB gelesen werden, würden fehlschlagen, aber das Schreiben von noch nicht persistierten Entitäten ist in Ordnung. Sie müssen jedoch separate Transaktionen verwenden, um zu verhindern, dass ein Fehler alles zurücksetzt, daher die withNewTransaction.

+0

keine Ahnung, was der Unterschied zwischen 'withNewTransaction' und' withTransaction' ist? Ersteres ist nicht dokumentiert. –

+1

[Unterschied hier erwähnt] (http://StackOverflow.com/a/17994623/2051952) und [Plan ist, dies zu Dokumenten in 2.4] (http: //jira.grails.org/browse/GRAILS-7093). Suchen Sie nach Kommentaren von Graeme. Ich frage mich, warum es nicht zu Dokumenten gemacht hat. Burt hat diese Methode in seinem Buch behandelt. @Don – dmahapatro

+1

Wenn Sie das TransactionStatus-Objekt ausgeben, gibt es eine Boolesche Eigenschaft namens newTransaction. Offenbar für withTransaction ist dies auf false gesetzt. Und basierend auf dem Verhalten (es wird nur ein beliebiges setRollbackOnly verwendet, um einen Fehler zu simulieren) scheint es eine einzige Transaktion zu verwenden, es sei denn, Sie verwenden withNewTransaction. – jrob

0

Könnten Sie versuchen, Validieren sie zuerst, und dann alle diejenigen, Speichern, die vergangen? Ich bin mir nicht sicher, ob es leistungsfähiger ist, aber es könnte ein bisschen sauberer sein. Etwas wie:

List<Book> books = createSomeBooks() 
List<Book> validatedBooks = books.findAll { it.validate() } 
validatedBooks*.save() 

Obwohl ich nicht sicher bin, ob .validate() die aus anderen Gründen wird es nicht versäumen verspricht zu speichern, und wenn die Daten unabhängig ist (dh eine eindeutige Einschränkung vergeht, bis das nächste Buch versucht auch zu sparen).

+0

Ich glaube nicht, dass dies zuverlässig ist, denn wie Sie hingewiesen haben, gibt es keine Garantie, dass ein validiertes Objekt tatsächlich speichern wird Der Status der Datenbank könnte sich zwischen dem Zeitpunkt der Validierung des Objekts und dem Zeitpunkt der Speicherung unterscheiden. –

0

Vielleicht könnten Sie groovy Meta-Programmierung verwenden & Grails dynamische Domain-Methoden?

In Bootstrap:

def grailsApplication 

    def init = { 

    List.metaClass.saveCollection = { 
     ApplicationContext context = (ApplicationContext) ServletContextHolder.getServletContext().getAttribute(GrailsApplicationAttributes.APPLICATION_CONTEXT); 
     SessionFactory sf = context.getBean('sessionFactory') 
     Session hsession = sf.openSession() 
     def notSaved = [] 
     delegate.each { 
      if(!it.trySave()) { 
       notSaved << it 
       hsession.close() 
       hsession = sf.openSession() 
      } 
     } 
     hsession.close() 
     return notSaved 
    } 

    grailsApplication.getArtefacts("Domain")*.clazz.each { clazz -> 
     def meta = clazz.metaClass 
     meta.trySave = { 
      def instance = delegate 
      def success = false 
      clazz.withTransaction { TransactionStatus status -> 
       try { 
        instance.save(failOnError: true) // ', flush: true' ? 
        success = true 
       } catch (ex) { 
        status.setRollbackOnly() 
       } 
      } 
      return success 
     } 
    } 
    } 

Und dann:

class TheController { 
    def index() { 
     List<Book> books = createSomeBooks() 

     def notSaved = books.saveCollection() 
     books.retainAll { !notSaved.contains(it) } 

     println "SAVED: " + books 
     println "NOT SAVED: " + notSaved 
    } 
} 

Natürlich muss es einige Kontrollen werden durchgeführt (zum Beispiel Liste enthält Domain-Klassen, etc.). Sie können auch passieren zu Schließungen spezifischen params diese flexibler zu gestalten (zB flush, failOnError, deepValidate, etc.)