Question

Résumé Un parent peut avoir beaucoup d'enfants. Comment écrire un service tel que, si après l’ajout d’un parent, une erreur se produise lors de l’ajout d’un enfant, la transaction entière est annulée. Par exemple, ajoutez le parent p1, ajoutez avec succès le fils c1, puis lorsqu’une erreur se produit lors de l’ajout du fils c2, p1 et c1 doivent être annulés.

Problème détaillé

Dans le code suivant, il existe une contrainte unique sur la propriété name de l'enfant. Donc, si vous essayez d'ajouter le même nom deux fois avec un parent différent, l'enregistrement enfant ne doit pas être ajouté et l'enregistrement parent doit être annulé.

Mon problème est que l'enregistrement parent n'est pas annulé.

J'utilise MySQL avec InnoDB avec Grails 1.2-M2 et Tomcat 6.018.

Source de données

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

J'ai les classes de domaine simples suivantes:

Parent :

class Parent {

    static hasMany = [ children : Child ]

    String  name

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

Enfant

class Child {

    static belongsTo = Parent

    String name

    Parent parent

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

SPG Saisie de données simple

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

Contrôleur

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 = {}

}

Service

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
    }

}
Était-ce utile?

La solution

Vous devez également vous assurer qu'une exception RuntimeException est levée dans le service pour que la transaction soit automatiquement annulée.

Je ferais donc ceci:

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
}

puis récupérez les exceptions dans le contrôleur et restituez les erreurs.

L’autre méthode consiste à activer les transactions automatiques et à utiliser Parent.withTransaction. et marquer manuellement la transaction pour l'annulation s'il y a une erreur de validation.

Autres conseils

Je crois que cela devrait être:

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

Vous pouvez également utiliser la propriété failOnError lors de l'enregistrement de vos objets de domaine. Si l'enregistrement échoue en raison d'une erreur de validation, une exception est générée.

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

Ce comportement peut également être activé globalement en définissant la propriété grails.gorm.failOnError dans grails-app / conf / Config.groovy sur true

.

Pour plus d'informations, consultez la documentation du guide de l'utilisateur pour 'save': http://grails.org/doc/latest/ref/Domain%20Classes/save.html

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top