Как заставить транзакции работать в Grails - PullRequest
9 голосов
/ 27 октября 2009

Резюме Родитель может иметь много детей. Как написать сервис так, чтобы, если после добавления родителя возникла ошибка при добавлении потомка, откатилась вся транзакция. Например, добавьте родительский элемент 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
    }

}

Ответы [ 3 ]

5 голосов
/ 27 октября 2009

Вам также необходимо убедиться, что 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. и вручную пометить транзакцию для отката в случае ошибки проверки.

3 голосов
/ 27 октября 2009

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

class AddRecordsService {
    static transactional = true;// note *static* not boolean
}
2 голосов
/ 27 октября 2009

В качестве альтернативы вы можете использовать свойство 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

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

...