Pregunta

Resumen Un padre puede tener muchos hijos. ¿Cómo se escribe un servicio de manera que, si después de agregar un elemento primario hay un error al agregar un elemento secundario, la transacción completa se revierte? Por ejemplo, agregue el padre p1, agregue con éxito el hijo c1, luego, al agregar el hijo c2, se produce un error, tanto p1 como c1 deben revertirse.

Problema detallado

En el siguiente código, existe una restricción única en la propiedad del nombre del elemento secundario. Entonces, si intenta agregar el mismo nombre dos veces con un padre diferente, entonces el registro hijo no debe agregarse y el registro padre debe revertirse.

Mi problema es que el registro principal no se revierte.

Estoy usando MySQL con InnoDB con Grails 1.2-M2 y Tomcat 6.018.

Fuente de datos

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

Tengo las siguientes clases de dominio simples:

Principal :

class Parent {

    static hasMany = [ children : Child ]

    String  name

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

Niño

class Child {

    static belongsTo = Parent

    String name

    Parent parent

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

GSP de entrada de datos 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>

Controlador

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

}

Servicio

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
    }

}
¿Fue útil?

Solución

También debe asegurarse de que se arroje una RuntimeException dentro del servicio para que la transacción se revierta automáticamente.

Entonces haría esto:

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
}

y luego captura excepciones en el controlador y procesa los errores.

La otra forma es desactivar las transacciones automáticas y usar Parent.withTransaction y marque manualmente la transacción para deshacerla si hay un error de validación.

Otros consejos

Creo que debería ser:

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

Alternativamente, podría usar la propiedad failOnError al guardar los objetos de su dominio; si el guardado falla por un error de validación, arrojará una excepción.

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

Este comportamiento también se puede habilitar globalmente estableciendo la propiedad grails.gorm.failOnError en grails-app / conf / Config.groovy en true

Para obtener más información, consulte los documentos de la Guía del usuario para 'guardar': http://grails.org/doc/latest/ref/Domain%20Classes/save.html

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top