2009-10-27 7 views
9

Sommario Un genitore può avere molti figli. Come si scrive un servizio tale che, se dopo aver aggiunto un genitore si verifica un errore durante l'aggiunta di un figlio, viene eseguito il rollback dell'intera transazione. Ad esempio, aggiungi genitore p1, aggiungi con successo child c1, quindi quando si aggiunge child c2 si verifica un errore, entrambi i p1 e c1 dovrebbero essere ripristinati.Come eseguire le transazioni in Grails

problema dettagliato

Nel codice seguente, v'è un vincolo univoco sulla proprietà nome del bambino. Pertanto, se si tenta di aggiungere lo stesso nome due volte con un genitore diverso, il record figlio non deve essere aggiunto e il record padre deve essere ripristinato.

Il mio problema è che il record padre non viene ripristinato.

Sto usando MySQL con InnoDB con Grails 1.2-M2 e Tomcat 6.018.

Data Source

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" 
     } 
    } 
} 

Ho le seguenti classi di dominio semplice:

Parent:

class Parent { 

    static hasMany = [ children : Child ] 

    String name 

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

Bambino

class Child { 

    static belongsTo = Parent 

    String name 

    Parent parent 

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

semplice inserimento dati 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 = {} 

} 

Servizio

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 
    } 

} 

risposta

5

È inoltre necessario assicurarsi che un RuntimeException è gettato all'interno del servizio in modo che la transazione deve essere rotolato automaticamente.

Così farei questo:

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 
} 

e poi prendere eccezioni nel controller e rendo gli errori.

L'altro modo è di attivare le transazioni automatiche e utilizzare Parent.withTransaction e contrassegnare manualmente la transazione per il rollback se c'è un errore di convalida.

+0

Grazie per aver aggiunto quei dettagli importanti. –

+0

> È inoltre necessario assicurarsi che venga generata una RuntimeException all'interno del servizio > affinché la transazione venga automaticamente ripristinata > indietro. Questo era il mio problema! Sembra che i graal dovrebbero farlo per convenzione. –

+0

Penso che ci sia un'opzione di configurazione in arrivo la versione 1.2 per fare save() lanciare eccezioni invece di restituire null se la validazione fallisce – leebutts

3

credo che dovrebbe essere:

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

Grazie! Sì, sicuramente dovrebbe essere statico. Tuttavia, non funziona ancora. Penso che in realtà dovrebbe essere impostato su true se non specificato. Ma rimuovere la linea tutti insieme non funziona neanche. Forse questo è un bug di Grails? Ho corretto il mio codice sopra. –

+0

Si scopre che boolean effettivamente funziona, ma dovrebbe essere statico. Il vero problema non è stato quello di lanciare un'eccezione come spiegato nella prossima risposta. –

2

In alternativa, è possibile utilizzare la proprietà failOnError durante il salvataggio degli oggetti dominio; se il salvataggio non riesce per un errore di convalida, verrà generata un'eccezione.

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

Questo comportamento può anche essere attivata a livello globale con impostando la proprietà grails.gorm.failOnError a graal-app/conf/Config.groovy a true

Per ulteriori informazioni, consultare la documentazione Guida per l'utente per 'salva': http://grails.org/doc/latest/ref/Domain%20Classes/save.html

+0

Sembra che theChild.save (failOnError: true) funzioni, ma non imposta il file di proprietà in Config.groovy. BTW - Ho una domanda di follow-up qui: http://stackoverflow.com/questions/1640666/how-to-know-the-cause-of-a-validation-error –