Grailsでトランザクションを機能させる方法
-
06-07-2019 - |
質問
概要 親は多くの子を持つことができます。親を追加した後に子を追加するときにエラーが発生した場合、トランザクション全体がロールバックされるように、サービスをどのように作成しますか。たとえば、親p1を追加し、子c1を正常に追加し、子c2を追加するときにエラーが発生した場合、p1とc1の両方をロールバックする必要があります。
詳細な問題
次のコードでは、子のnameプロパティに一意の制約があります。したがって、同じ親を別の親に2回追加しようとすると、子レコードは追加されず、親レコードはロールバックされます。
私の問題は、親レコードがロールバックされていないことです。
MySQL w / 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-app / conf / Config.groovyのgrails.gorm.failOnErrorプロパティをtrueに設定することでグローバルに有効にすることもできます
詳細については、「save」のユーザーガイドドキュメントを参照してください: http://grails.org/doc/latest/ref/Domain%20Classes/save.html