目的
GrailsのGORMはとても強力だけど、その代わりに動作がドキュメントを読むだけでは中々理解しづらい。
そこで今回は、デフォルト状態でhasManyのカスケードがどのように動作するのかを検証した。
サンプルソース
以下の2つのドメインを用意。
内容としては、Personは複数のHobbyを保持する、といったもの。
誰でも趣味は複数もってるよね、ということ。
class Person {
String name
Date dateCreated
Date lastUpdated
static hasMany = [hobbies:Hobby]
static constraints = {
}
}
class Hobby {
String name
Date dateCreated
Date lastUpdated
static constraints = {
}
}
なお、生成されるテーブルは以下のとおり。
中身は当然空っぽ
以下のコントーラを用意して、それぞれアクセスしてテーブルの内容を確認していく。
class HelloController {
// 初期データの登録
def index() {
def p = new Person(name:"koji").save()
def h = new Hobby(name:"computer").save()
render("index ok")
}
// PersonインスタンスにHobbyインスタンスを追加する。
def index2() {
def p = Person.findByName("koji")
def h = Hobby.findByName("computer")
p.addToHobbies(h)
p.save()
render("index2 ok")
}
// Hobbyインスタンスを持つPersonインスタンスの更新(Hobbyの値は変更しない)
def index3(){
def p = Person.findByName("koji")
def h = Hobby.findByName("computer")
p.name +="!!"
p.save()
render("index3 ok")
}
// HobbyインスタンスをもつPersonインスタンスの更新(Hobbyの値を変更する)
def index4() {
def p = Person.findByName("koji!!")
p.hobbies.each { it.name += "_upated" }
p.save()
render("index4 ok")
}
// Hobbyインスタンスを持つPersonインスタンスの削除
def index5() {
def p = Person.findByName("koji!!")
p.delete()
render("index5 ok")
}
}
実際にアクションにアクセスして確認
indexアクション
データがそれぞれ保存されるのみ。
def index() {
def p = new Person(name:"koji").save()
def h = new Hobby(name:"computer").save()
render("index ok")
}
DBは以下のようになる。
index2アクション
// PersonインスタンスにHobbyインスタンスを追加する。
def index2() {
def p = Person.findByName("koji")
def h = Hobby.findByName("computer")
p.addToHobbies(h)
p.save()
render("index2 ok")
}
DBは以下のようになる。
PERSON_HOBBYテーブルに関連を表すレコードがINSERTされる。
また、PersonにHobbyが追加されたので、当然Personの該当レコードがUPDATEされる。
しかし、Hobbyインスタンス自体は何も値が変更されていないので、UPDATEされない。
index3アクション
// Hobbyインスタンスを持つPersonインスタンスの更新(Hobbyの値は変更しない)
def index3(){
def p = Person.findByName("koji")
def h = Hobby.findByName("computer")
p.name +="!!"
p.save()
render("index3 ok")
}
DBは以下のようになる。
今回はPerson.nameの値を更新したのみなので、UPDATEされるテーブルも当然personテーブルの該当レコードのみ。
index4アクション
// HobbyインスタンスをもつPersonインスタンスの更新(Hobbyの値を変更する)
def index4() {
def p = Person.findByName("koji!!")
p.hobbies.each { it.name += "_upated" }
p.save()
render("index4 ok")
}
DBは以下のようになる。
ココが味噌。
確かにPersonが持つHobbyインスタンスの値を全て変更したが、saveメソッドを実行したのはPersonインスタンスのみ。
にもかかわらず、Hobbyの値が更新されている。
これを、Personの更新がHobbyにカスケードされたという。
なお、Personのsaveメソッドを実行したのに、Personのレコード自体は更新されていない。
この事から、Grailsは更新(UPDATE)の際に、値自体に変化が無ければ実際のSQL(UPDATE)は実行しないことが分かる。
index3でPersonのsaveメソッドを実行した際に、Personが持っているHobbyインスタンスも本来は更新処理がカスケードされるが、Hobbyの値自体に変化が無かったので、結果的にUPDATEがされなかったということになる。
index5アクション
// Hobbyインスタンスを持つPersonインスタンスの削除
def index5() {
def p = Person.findByName("koji!!")
p.delete()
render("index5 ok")
}
DBは以下のようになる。
ちゃんとデータがPERSONテーブルとPERSON_HOBBYテーブルから削除されている。
削除に関してはカスケードされない。
そのため、Personの持っていたHobbyに関するデータ自体は、DBから削除されることはない。
削除もカスケードしたい場合は以下の2つの方法がある。
1.Hobbyにstatic belongsTo = [person:Person]
を付与する。
2.Personに以下のコードを追加する。
static mapping = {
hobbies cascade: "all-delete-orphan"
}
1番に関しては、スマートだけど、そもそもhasManyではbelongsToの有無でテーブルの構成が変わる。(デフォルトの自動生成の場合)
2番に関しては、personインスタンスからHobbyをremoveすると、removeされたHobbyがPersonから削除され、さらにそのHobbyレコード自体がデータベースからも削除されるようになる。
1番のbelongsToに関しては、今回はそもそもHobbyはいわゆるマスターデータなので、Personインスタンスに従属する(belongsTo)というのはおかしい。
なので、削除をカスケードしたい、Hobbyインスタンスを持っているPersonをHobbyドメインから検索したいという目的の為に使うべきじゃないと思う。