Вопрос

Краткие сведения У одного родителя может быть много детей.Как вы пишете сервис таким образом, чтобы, если после добавления родительского элемента возникает ошибка при добавлении дочернего, вся транзакция откатывалась.Например, добавляем родительский p1, успешно добавляем дочерний c1, затем при добавлении дочернего c2 возникает ошибка, следует выполнить откат как p1, так и c1.

Подробная проблема

В следующем коде существует уникальное ограничение на свойство name дочернего элемента.Таким образом, если вы попытаетесь добавить одно и то же имя дважды с другим родительским именем, то дочерняя запись не должна добавляться, а родительская запись должна быть откатана.

Моя проблема в том, что родительская запись не откатывается.

Я использую MySQL с InnoDB с Grails 1.2-M2 и Tomcat 6.018.

Источник данных

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

У меня есть следующие простые доменные классы:

Родитель:

class Parent {

    static hasMany = [ children : Child ]

    String  name

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

Ребенок

class Child {

    static belongsTo = Parent

    String name

    Parent parent

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

Простой ввод данных 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>

Контроллер

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

}

Обслуживание

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
    }

}
Это было полезно?

Решение

Вам также необходимо убедиться, что внутри службы вызвано исключение RuntimeException, чтобы транзакция была автоматически откатана.

Так что я бы сделал это:

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
}

а затем перехватывать исключения в контроллере и отображать ошибки.

Другой способ - отключить автоматические транзакции и использовать Parent.withTransaction и вручную пометить транзакцию для отката в случае ошибки проверки.

Другие советы

Я считаю, что так и должно быть:

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

В качестве альтернативы вы могли бы использовать свойство failOnError при сохранении объектов вашего домена - если при сохранении произойдет сбой из-за ошибки проверки, то будет выдано исключение.

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

Это поведение также можно включить глобально, установив для свойства grails.gorm.failOnError в grails-app/conf/Config.groovy значение true

Для получения дополнительной информации см. раздел "Сохранить" в руководстве пользователя docs: http://grails.org/doc/latest/ref/Domain%20Classes/save.html

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top