2009-10-27 7 views
9

Zusammenfassung Ein Elternteil kann viele Kinder haben. Wie schreiben Sie einen Dienst, bei dem nach dem Hinzufügen eines Elternelements beim Hinzufügen eines untergeordneten Objekts ein Fehler auftritt, wird die gesamte Transaktion zurückgesetzt. Fügen Sie beispielsweise übergeordnete p1 hinzu, fügen Sie erfolgreich untergeordnete c1 hinzu, und fügen Sie dann unter c2 einen Fehler ein, sollten sowohl p1 als auch c1 zurückgesetzt werden.So machen Sie Transaktionen in Grails

Detaillierte Problem

Im folgenden Code gibt es eine eindeutige Einschränkung auf den Namen des Vermögens des Kindes. Wenn Sie also versuchen, denselben Namen zweimal mit einem anderen übergeordneten Element hinzuzufügen, sollte der untergeordnete Datensatz nicht hinzugefügt und der übergeordnete Datensatz zurückgesetzt werden.

Mein Problem ist, dass der übergeordnete Datensatz nicht zurückgesetzt wird.

Ich verwende MySQL w/InnoDB mit Grails 1.2-M2 und Tomcat 6.018.

Datenquelle

import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration 
dataSource { 
    configClass = GrailsAnnotationConfiguration.class 
    pooled = true 
    driverClassName = "com.mysql.jdbc.Driver" 
    dialect = org.hibernate.dialect.MySQLInnoDBDialect 
    zeroDateTimeBehavior="convertToNull" //Java can't convert ''0000-00-00 00:00:00' to TIMESTAMP 
    username = "root" 
    password = "12345" 
    loggingSql=false 
} 

hibernate { 
    cache.use_second_level_cache=true 
    cache.use_query_cache=true 
    cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider' 
} 
// environment specific settings 
environments { 
    development { 
     dataSource { 
      dbCreate = "create-drop" // one of 'create', 'create-drop','update' 
       url = "jdbc:mysql://localhost:3306/transtest?zeroDateTimeBehavior=convertToNull" 

     } 
    } 
    test { 
     dataSource { 
      dbCreate = "update" 
      url = "jdbc:mysql://localhost:3306/transtest?zeroDateTimeBehavior=convertToNull" 

     } 
    } 
    production { 
     dataSource { 
      dbCreate = "update" 
      url = "jdbc:mysql://localhost:3306/transtest?zeroDateTimeBehavior=convertToNull" 
     } 
    } 
} 

Ich habe die folgenden einfachen Domain-Klassen:

Eltern:

class Parent { 

    static hasMany = [ children : Child ] 

    String name 

    static constraints = { 
     name(blank:false,unique:true) 
    } 
} 

Kinder

class Child { 

    static belongsTo = Parent 

    String name 

    Parent parent 

    static constraints = { 
     name(blank:false,unique:true) 
    } 
} 

Einfache Dateneingabe GSP

<html> 
    <head> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> 
    <title>Sample title</title> 
    </head> 
    <body> 
    <h1>Add A Record</h1> 
    <g:form action="add" name="doAdd"> 
    <table> 
     <tr> 
     <td> 
      Parent Name 
     </td> 
     <td> 
      Child Name 
     </td> 
     </tr> 
     <tr> 
     <td> 
      <g:textField name="parentName" /> 
     </td> 
     <td> 
      <g:textField name="childName" /> 
     </td> 
     </tr> 
     <tr><td><g:submitButton name="update" value="Update" /></td></tr> 
    </table> 
    </g:form> 
</body> 
</html> 

-Controller

class AddrecordController { 

    def addRecordsService 

    def index = { 
     redirect action:"show", params:params 
    } 

    def add = { 
     println "do add" 


     addRecordsService.addAll(params) 
     redirect action:"show", params:params 

    } 

    def show = {} 

} 

Dienst

class AddRecordsService { 

    // boolean transactional = true //shouldn't this be all I need? 
     static transactional = true // this should work but still doesn't nor does it work if the line is left out completely 
    def addAll(params) { 
     println "add all" 
     println params 
     def Parent theParent = addParent(params.parentName) 
     def Child theChild = addChild(params.childName,theParent) 
     println theParent 
     println theChild 
    } 

    def addParent(pName) { 
     println "add parent: ${pName}" 
     def theParent = new Parent(name:pName) 
     theParent.save() 
     return theParent 
    } 

    def addChild(cName,Parent theParent) { 
     println "add child: ${cName}" 
     def theChild = new Child(name:cName,parent:theParent) 
     theChild.save() 
     return theChild 
    } 

} 

Antwort

5

Sie müssen auch ein Runtime sicherstellen, dass innerhalb des Service, um für die Transaktion ausgelöst wird, automatisch zurückgerollt werden.

Also würde ich dies tun:

def addParent(pName) { 
     println "add parent: ${pName}" 
     def theParent = new Parent(name:pName) 
     if(!theParent.save()){ 
      throw new RuntimeException('unable to save parent') 
     } 
     return theParent 
    } 

def addChild(cName,Parent theParent) { 
    println "add child: ${cName}" 
    def theChild = new Child(name:cName,parent:theParent) 
    theChild.save() 
    if(!child.save()){ 
     throw new RuntimeException('unable to save child') 
    } 
    return theChild 
} 

und dann Ausnahmen in der Steuerung fangen und die Fehler machen.

Die andere Möglichkeit besteht darin, automatische Transaktionen zu deaktivieren und Parent.withTransaction zu verwenden und die Transaktion manuell für Rollback zu markieren, wenn ein Validierungsfehler vorliegt.

+0

Danke für das Hinzufügen dieser wichtigen Details. –

+0

> Sie müssen auch sicherstellen, dass eine RuntimeException innerhalb des > Dienstes ausgelöst wird, damit die Transaktion automatisch gerollt wird > zurück. Das war mein Problem! Sieht so aus, als würden die Grals dies per Konvention tun. –

+0

Ich denke, es gibt eine Konfigurationsoption, Version 1.2 zu kommen, um save() auszulösen, anstatt Null zurückzugeben, wenn die Validierung fehlschlägt – leebutts

3

Ich glaube, es sein sollte:

class AddRecordsService { 
    static transactional = true;// note *static* not boolean 
} 
+0

Danke! Ja, es sollte definitiv statisch sein. Es funktioniert jedoch immer noch nicht. Ich denke, es ist eigentlich auf True, wenn nicht angegeben. Aber auch das Entfernen der Linie funktioniert nicht. Vielleicht ist das ein Grals Bug? Ich habe meinen obigen Code korrigiert. –

+0

Stellt sich heraus, dass boolean tatsächlich funktioniert, aber sollte statisch sein. Das eigentliche Problem war, keine Ausnahme zu machen, wie in der nächsten Antwort erklärt. –

2

Alternativ können Sie die failOnError-Eigenschaft beim Speichern Ihrer Domain-Objekte verwenden - wenn das Speichern für einen Validierungsfehler fehlschlägt, wird eine Ausnahme ausgelöst.

def addChild(cName,Parent theParent) { 
    println "add child: ${cName}" 
    def theChild = new Child(name:cName,parent:theParent) 
    theChild.save(failOnError:true) 
    return theChild 
} 

Dieses Verhalten mit global auch in Grails-app/conf/Config.groovy auf true

indem

Für weitere Informationen, um die grails.gorm.failOnError Eigenschaft aktiviert werden kann, für die Bedienungsanleitung docs sehen 'speichern': http://grails.org/doc/latest/ref/Domain%20Classes/save.html

+0

Es scheint, dass theChild.save (failOnError: true) funktioniert, aber das Festlegen der Eigenschaftendatei in Config.groovy nicht. BTW - Ich habe eine Follow-up-Frage hier: http://StackOverflow.com/Questions/1640666/How-to-know-the-cause-of-a-validation-error –