Domanda

Riepilogo Un genitore può avere molti figli. Come si scrive un servizio in modo tale che, se dopo l'aggiunta di un genitore si verifica un errore durante l'aggiunta di un figlio, viene eseguito il rollback dell'intera transazione. Ad esempio, aggiungere il genitore p1, aggiungere correttamente il figlio c1, quindi quando si aggiunge un figlio c2 si verifica un errore, è necessario ripristinare sia p1 che c1.

Problema dettagliato

Nel codice seguente, esiste un vincolo univoco sulla proprietà name del figlio. 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 non viene eseguito il rollback del record principale.

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

Fonte dei dati

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 semplici classi di dominio:

principale :

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

SPG immissione dati semplici

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

Regolatore

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
    }

}
È stato utile?

Soluzione

È inoltre necessario assicurarsi che all'interno del servizio venga generata un'eccezione RuntimeException per poter ripristinare automaticamente la transazione.

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

, quindi rileva le eccezioni nel controller e visualizza gli errori.

L'altro modo è di disattivare le transazioni automatiche e utilizzare Parent.withTransaction e contrassegnare manualmente la transazione per il rollback in caso di errore di convalida.

Altri suggerimenti

Credo che dovrebbe essere:

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

In alternativa puoi usare la proprietà failOnError quando salvi gli oggetti del tuo dominio - se il salvataggio fallisce per un errore di validazione, allora genererà 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 abilitato a livello globale impostando la proprietà grails.gorm.failOnError in grails-app / conf / Config.groovy su true

Per ulteriori informazioni, consultare la documentazione del Manuale dell'utente per "save": http://grails.org/doc/latest/ref/Domain%20Classes/save.html

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top