LoginSignup
2
3

More than 5 years have passed since last update.

[Grails]GORMの動作(プログラム・HasMany編)

Posted at

目的

GrailsのGORMはとても強力だけど、その代わりに動作がドキュメントを読むだけでは中々理解しづらい。
そこで今回は、デフォルト状態でhasManyのカスケードがどのように動作するのかを検証した。

サンプルソース

以下の2つのドメインを用意。
内容としては、Personは複数のHobbyを保持する、といったもの。
誰でも趣味は複数もってるよね、ということ。

Person.groovy
class Person {

    String name
    Date dateCreated
    Date lastUpdated
    static hasMany = [hobbies:Hobby]
    static constraints = {
    }
}
Hobby.groovy
class Hobby {

    String name
    Date dateCreated
    Date lastUpdated
    static constraints = {
    }
}

なお、生成されるテーブルは以下のとおり。

1.png

中身は当然空っぽ

2.png

以下のコントーラを用意して、それぞれアクセスしてテーブルの内容を確認していく。

HelloController.groovy
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は以下のようになる。

3.png

index2アクション

// PersonインスタンスにHobbyインスタンスを追加する。
def index2() {
    def p = Person.findByName("koji")
    def h = Hobby.findByName("computer")
    p.addToHobbies(h)
    p.save()
    render("index2 ok")
}

DBは以下のようになる。

4.png

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は以下のようになる。

5.png

今回は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は以下のようになる。

6.png

ココが味噌。
確かに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は以下のようになる。

7.png

ちゃんとデータが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ドメインから検索したいという目的の為に使うべきじゃないと思う。

2
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
3