Grailsの関連の型での動作の違いを理解する

ドメイン間の関連の型をどの様に設定するかでの挙動の違いです。リファレンスに書いてあることですが、いろいろ動かしながら検証してみました。

ログとかだらだら長くて見にくいですが、面白いので貼り付けときました。

Bookドメイン

class Book {

    String title

    static belongsTo = [author: Author]

}

Authorドメイン

class Author {

    String name

    static hasMany = [books: Book]

}

このときのスキーマは以下のようになります(H2の場合)。

create table author (
    id bigint generated by default as identity,
    version bigint not null,
    name varchar(255) not null,
    primary key (id)
);

create table book (
    id bigint generated by default as identity,
    version bigint not null,
    author_id bigint not null,
    title varchar(255) not null,
    primary key (id)
);

相互に関連が設定されているのでmappingテーブルが作成されていません。

Authorでbooksというone-to-manyな関連を定義していますが、特に型を指定していない場合はデフォルトjava.util.Setが使われます。

ここでテストを実行してみます。

class AuthorSpec extends IntegrationSpec {

    def setup() {
        Author.withNewSession {
            def author = new Author(name: "dummy")
            author.addToBooks(new Book(title: "hoge"))
            author.addToBooks(new Book(title: "foo"))
            author.addToBooks(new Book(title: "bar"))
            author.save()
        }
    }

    def "addToBooksでbookを保存する"() {
        setup:
        def author = Author.findByName("dummy")

        when:
        author.addToBooks(new Book(title: "test"))
        author.save(flush: true)

        then:
        Book.findByTitle("test").author == author
    }

}

実際に実行しみるとテストメソッドでは以下のSQLが発行されます。

2012-07-18 16:52:19,360 [main] DEBUG hibernate.SQL  - 
    select
        this_.id as id1_0_,
        this_.version as version1_0_,
        this_.name as name1_0_ 
    from
        author this_ 
    where
        this_.name=? limit ?
2012-07-18 16:52:19,408 [main] DEBUG hibernate.SQL  - 
    select
        books0_.author_id as author3_1_1_,
        books0_.id as id1_,
        books0_.id as id0_0_,
        books0_.version as version0_0_,
        books0_.author_id as author3_0_0_,
        books0_.title as title0_0_ 
    from
        book books0_ 
    where
        books0_.author_id=?
2012-07-18 16:52:19,471 [main] DEBUG hibernate.SQL  - 
    insert 
    into
        book
        (id, version, author_id, title) 
    values
        (null, ?, ?, ?)
2012-07-18 16:52:19,507 [main] DEBUG hibernate.SQL  - 
    update
        author 
    set
        version=?,
        name=? 
    where
        id=? 
        and version=?
2012-07-18 16:52:19,520 [main] DEBUG hibernate.SQL  - 
    select
        this_.id as id0_0_,
        this_.version as version0_0_,
        this_.author_id as author3_0_0_,
        this_.title as title0_0_ 
    from
        book this_ 
    where
        this_.title=? limit ?

面白いのはauthor.addToBooksしたタイミンで一度、そのときの関連全てを取得するためのselectが走ることです。これは関連がjava.util.Setが定義されているからで、books内のエンティティがユニークであることを保証するために、関連がこのタイミングでレイジーにロードされます。試しに、重複した要素を追加してみます。

    def "addToBooksで重複したbookを追加する"() {
        setup:
        def author = Author.findByName("dummy")
        def book = Book.findByTitle("hoge")

        when:
        author.addToBooks(book)
        author.save(flush: true)

        then:
        author.books.size() == 3
    }

実行すると以下の様なSQLが実行されます。

2012-07-18 19:05:19,917 [main] DEBUG hibernate.SQL  - 
    select
        this_.id as id0_0_,
        this_.version as version0_0_,
        this_.name as name0_0_ 
    from
        author this_ 
    where
        this_.name=? limit ?
2012-07-18 19:05:20,009 [main] DEBUG hibernate.SQL  - 
    select
        this_.id as id1_0_,
        this_.version as version1_0_,
        this_.author_id as author3_1_0_,
        this_.title as title1_0_ 
    from
        book this_ 
    where
        this_.title=? limit ?
2012-07-18 19:05:20,036 [main] DEBUG hibernate.SQL  - 
    select
        books0_.author_id as author3_0_1_,
        books0_.id as id1_,
        books0_.id as id1_0_,
        books0_.version as version1_0_,
        books0_.author_id as author3_1_0_,
        books0_.title as title1_0_ 
    from
        book books0_ 
    where
        books0_.author_id=?

author.addToBooks(book)で要素を追加したタイミングで関連を取得していますが、すでに追加されている要素なので、重複して追加されずsave()しても特に何も起きません。これとは別に明示的にSortedSetで宣言して関連先のcompareToを実装して、ソート済み状態にすることも出来ます。

次にAuthorドメインでbooksの型を明示的にListに設定してみます。

class Author {

    String name
    
    List books

    static hasMany = [books: Book]

}

こんどは以下の様なスキーマが生成されるようになります。

create table author (
    id bigint generated by default as identity,
    version bigint not null,
    name varchar(255) not null,
    primary key (id)
);

create table book (
    id bigint generated by default as identity,
    version bigint not null,
    author_id bigint not null,
    title varchar(255) not null,
    books_idx integer,
    primary key (id)
);

books_idxというbooksの順序を保存するためのカラムが追加されているのが特徴的です。こいつの動作を検証するテストを書いてみます。

def "booksに追加したり、ソートしたり、削除したり"() {
    setup:
    def author = Author.findByName("dummy")

    when:
    println "新規にBookを追加 ${'>' * 100}"
    author.addToBooks(new Book(title: "test"))
    author.save(flush: true)

    then:
    Author.withNewSession { Author.get(author.id).books*.title } == ["hoge", "foo", "bar", "test"]

    when:
    println "ソートして保存 ${'>' * 100}"
    author.books.sort { it.title }
    author.save(flush: true)

    then:
    Author.withNewSession { Author.get(author.id).books*.title } == ["bar", "foo", "hoge", "test"]

    when:
    println "要素を削除して保存 ${'>' * 100}"
    author.books.remove(1).delete()
    author.save(flush: true)

    then:
    Author.withNewSession { Author.get(author.id).books*.title } == ["bar", "hoge", "test"]
}

ちょっとださいけどログがみやすいようにprint仕込んでます。あとthenで検証する際に1次キャッシュを明示的に使用しないようにwithNewSessionでクエリを実行しています。これを実行すると以下の様なログになりました(バインドされている値も一緒にだしているのでかなり長めです...)。

2012-07-19 23:34:07,569 [main] DEBUG org.hibernate.SQL - 
    select
        this_.id as id1_0_,
        this_.version as version1_0_,
        this_.name as name1_0_ 
    from
        author this_ 
    where
        this_.name=? limit ?
2012-07-19 23:34:07,569 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [VARCHAR] - dummy
2012-07-19 23:34:07,575 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id1_0_]
2012-07-19 23:34:07,577 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version1_0_]
2012-07-19 23:34:07,578 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [dummy] as column [name1_0_]
新規にBookを追加 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
2012-07-19 23:34:07,605 [main] DEBUG org.hibernate.SQL - 
    select
        books0_.author_id as author3_1_1_,
        books0_.id as id1_,
        books0_.books_idx as books5_1_,
        books0_.id as id5_0_,
        books0_.version as version5_0_,
        books0_.author_id as author3_5_0_,
        books0_.title as title5_0_ 
    from
        book books0_ 
    where
        books0_.author_id=?
2012-07-19 23:34:07,605 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,608 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id5_0_]
2012-07-19 23:34:07,609 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,609 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,609 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [hoge] as column [title5_0_]
2012-07-19 23:34:07,609 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,609 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id1_]
2012-07-19 23:34:07,612 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [books5_1_]
2012-07-19 23:34:07,612 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [id5_0_]
2012-07-19 23:34:07,612 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,612 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [foo] as column [title5_0_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [id1_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [books5_1_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [id5_0_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [bar] as column [title5_0_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [id1_]
2012-07-19 23:34:07,613 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [books5_1_]
2012-07-19 23:34:07,643 [main] DEBUG org.hibernate.SQL - 
    insert 
    into
        book
        (id, version, title, author_id, books_idx) 
    values
        (null, ?, ?, ?, ?)
2012-07-19 23:34:07,643 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 0
2012-07-19 23:34:07,643 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [VARCHAR] - test
2012-07-19 23:34:07,644 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - 1
2012-07-19 23:34:07,645 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [INTEGER] - 3
2012-07-19 23:34:07,668 [main] DEBUG org.hibernate.SQL - 
    update
        author 
    set
        version=?,
        name=? 
    where
        id=? 
        and version=?
2012-07-19 23:34:07,669 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,669 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [VARCHAR] - dummy
2012-07-19 23:34:07,669 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - 1
2012-07-19 23:34:07,669 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [BIGINT] - 0
2012-07-19 23:34:07,673 [main] DEBUG org.hibernate.SQL - 
    update
        book 
    set
        author_id=?,
        books_idx=? 
    where
        id=?
2012-07-19 23:34:07,673 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,673 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [INTEGER] - 3
2012-07-19 23:34:07,673 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - 4
2012-07-19 23:34:07,688 [main] DEBUG org.hibernate.SQL - 
    select
        author0_.id as id1_0_,
        author0_.version as version1_0_,
        author0_.name as name1_0_ 
    from
        author author0_ 
    where
        author0_.id=?
2012-07-19 23:34:07,688 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,689 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [version1_0_]
2012-07-19 23:34:07,689 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [dummy] as column [name1_0_]
2012-07-19 23:34:07,692 [main] DEBUG org.hibernate.SQL - 
    select
        books0_.author_id as author3_1_1_,
        books0_.id as id1_,
        books0_.books_idx as books5_1_,
        books0_.id as id5_0_,
        books0_.version as version5_0_,
        books0_.author_id as author3_5_0_,
        books0_.title as title5_0_ 
    from
        book books0_ 
    where
        books0_.author_id=?
2012-07-19 23:34:07,693 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,694 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id5_0_]
2012-07-19 23:34:07,697 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,697 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,697 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [hoge] as column [title5_0_]
2012-07-19 23:34:07,697 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id1_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [books5_1_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [id5_0_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [foo] as column [title5_0_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [id1_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [books5_1_]
2012-07-19 23:34:07,698 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [id5_0_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [bar] as column [title5_0_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [id1_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [books5_1_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [4] as column [id5_0_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [test] as column [title5_0_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [4] as column [id1_]
2012-07-19 23:34:07,699 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [books5_1_]
ソートして保存 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
2012-07-19 23:34:07,744 [main] DEBUG org.hibernate.SQL - 
    update
        author 
    set
        version=?,
        name=? 
    where
        id=? 
        and version=?
2012-07-19 23:34:07,746 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 2
2012-07-19 23:34:07,746 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [VARCHAR] - dummy
2012-07-19 23:34:07,746 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - 1
2012-07-19 23:34:07,746 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [BIGINT] - 1
2012-07-19 23:34:07,747 [main] DEBUG org.hibernate.SQL - 
    update
        book 
    set
        author_id=?,
        books_idx=? 
    where
        id=?
2012-07-19 23:34:07,747 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,747 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [INTEGER] - 0
2012-07-19 23:34:07,747 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - 3
2012-07-19 23:34:07,747 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,747 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [INTEGER] - 2
2012-07-19 23:34:07,748 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - 1
2012-07-19 23:34:07,757 [main] DEBUG org.hibernate.SQL - 
    select
        author0_.id as id1_0_,
        author0_.version as version1_0_,
        author0_.name as name1_0_ 
    from
        author author0_ 
    where
        author0_.id=?
2012-07-19 23:34:07,758 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,758 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [version1_0_]
2012-07-19 23:34:07,758 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [dummy] as column [name1_0_]
2012-07-19 23:34:07,759 [main] DEBUG org.hibernate.SQL - 
    select
        books0_.author_id as author3_1_1_,
        books0_.id as id1_,
        books0_.books_idx as books5_1_,
        books0_.id as id5_0_,
        books0_.version as version5_0_,
        books0_.author_id as author3_5_0_,
        books0_.title as title5_0_ 
    from
        book books0_ 
    where
        books0_.author_id=?
2012-07-19 23:34:07,760 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,761 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id5_0_]
2012-07-19 23:34:07,762 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,762 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,762 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [hoge] as column [title5_0_]
2012-07-19 23:34:07,762 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,762 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id1_]
2012-07-19 23:34:07,762 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [books5_1_]
2012-07-19 23:34:07,762 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [id5_0_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [foo] as column [title5_0_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [id1_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [books5_1_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [id5_0_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [bar] as column [title5_0_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,763 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [id1_]
2012-07-19 23:34:07,764 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [books5_1_]
2012-07-19 23:34:07,764 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [4] as column [id5_0_]
2012-07-19 23:34:07,764 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,764 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,764 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [test] as column [title5_0_]
2012-07-19 23:34:07,764 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,764 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [4] as column [id1_]
2012-07-19 23:34:07,765 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [books5_1_]
要素を削除して保存 >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
2012-07-19 23:34:07,789 [main] DEBUG org.hibernate.SQL - 
    update
        author 
    set
        version=?,
        name=? 
    where
        id=? 
        and version=?
2012-07-19 23:34:07,790 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 3
2012-07-19 23:34:07,790 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [VARCHAR] - dummy
2012-07-19 23:34:07,790 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - 1
2012-07-19 23:34:07,790 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [4] as [BIGINT] - 2
2012-07-19 23:34:07,790 [main] DEBUG org.hibernate.SQL - 
    update
        book 
    set
        author_id=?,
        books_idx=? 
    where
        id=?
2012-07-19 23:34:07,791 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,791 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [INTEGER] - 1
2012-07-19 23:34:07,791 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - 1
2012-07-19 23:34:07,791 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,791 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [INTEGER] - 2
2012-07-19 23:34:07,791 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [3] as [BIGINT] - 4
2012-07-19 23:34:07,792 [main] DEBUG org.hibernate.SQL - 
    delete 
    from
        book 
    where
        id=? 
        and version=?
2012-07-19 23:34:07,793 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 2
2012-07-19 23:34:07,793 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [2] as [BIGINT] - 0
2012-07-19 23:34:07,810 [main] DEBUG org.hibernate.SQL - 
    select
        author0_.id as id1_0_,
        author0_.version as version1_0_,
        author0_.name as name1_0_ 
    from
        author author0_ 
    where
        author0_.id=?
2012-07-19 23:34:07,812 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,812 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [version1_0_]
2012-07-19 23:34:07,812 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [dummy] as column [name1_0_]
2012-07-19 23:34:07,815 [main] DEBUG org.hibernate.SQL - 
    select
        books0_.author_id as author3_1_1_,
        books0_.id as id1_,
        books0_.books_idx as books5_1_,
        books0_.id as id5_0_,
        books0_.version as version5_0_,
        books0_.author_id as author3_5_0_,
        books0_.title as title5_0_ 
    from
        book books0_ 
    where
        books0_.author_id=?
2012-07-19 23:34:07,815 [main] TRACE org.hibernate.type.descriptor.sql.BasicBinder - binding parameter [1] as [BIGINT] - 1
2012-07-19 23:34:07,816 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id5_0_]
2012-07-19 23:34:07,818 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,818 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,818 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [hoge] as column [title5_0_]
2012-07-19 23:34:07,818 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,818 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [id1_]
2012-07-19 23:34:07,818 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [books5_1_]
2012-07-19 23:34:07,818 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [id5_0_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [bar] as column [title5_0_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [3] as column [id1_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [books5_1_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [4] as column [id5_0_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [0] as column [version5_0_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_5_0_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [test] as column [title5_0_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [1] as column [author3_1_1_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [4] as column [id1_]
2012-07-19 23:34:07,819 [main] TRACE org.hibernate.type.descriptor.sql.BasicExtractor - found [2] as column [books5_1_]

これからわかったことはaddToBooksのタイミングで一度関連をレイジーに取りに行く、その時のクエリにidxでorderbyしていない、これはgrails?側でソートきかせている?あと、ソートしたり、要素を消したりするとidxのupdateが走るといったとこでしょうか。これは意図せず大量のUPDATE文走らせそうで注意。

こんどはHibernateのBag型をつかってみます。Grailsでこれを使うにはCollection型で宣言する必要があります。

class Author {

    String name

    Collection books

    static hasMany = [books: Book]

}

んで以下のテストを実行。

def "addToBooksでbookを保存する"() {
    setup:
    def author = Author.findByName("dummy")

    when:
    author.addToBooks(new Book(title: "test"))
    author.save(flush: true)

    then:
    Author.withNewSession { Author.get(author.id).books*.title } as Set == ['hoge', 'foo', 'bar', 'test'] as Set
}

実行ログ。

2012-07-21 14:53:23,117 [main] DEBUG org.hibernate.SQL - 
    select
        this_.id as id1_0_,
        this_.version as version1_0_,
        this_.name as name1_0_ 
    from
        author this_ 
    where
        this_.name=? limit ?
2012-07-21 14:53:23,168 [main] DEBUG org.hibernate.SQL - 
    insert 
    into
        book
        (id, version, author_id, title) 
    values
        (null, ?, ?, ?)
2012-07-21 14:53:23,196 [main] DEBUG org.hibernate.SQL - 
    update
        author 
    set
        version=?,
        name=? 
    where
        id=? 
        and version=?
2012-07-21 14:53:23,214 [main] DEBUG org.hibernate.SQL - 
    select
        author0_.id as id1_0_,
        author0_.version as version1_0_,
        author0_.name as name1_0_ 
    from
        author author0_ 
    where
        author0_.id=?
2012-07-21 14:53:23,218 [main] DEBUG org.hibernate.SQL - 
    select
        books0_.author_id as author3_1_1_,
        books0_.id as id1_,
        books0_.id as id3_0_,
        books0_.version as version3_0_,
        books0_.author_id as author3_3_0_,
        books0_.title as title3_0_ 
    from
        book books0_ 
    where
        books0_.author_id=?

特徴はaddToBooksしたタイミングで、関連を引っ張るselectが走りません。これはBag型がソート、重複を管理しないためです。

ということで改めてSet、List、Collectionを比較してみると、特に制約がないならCollectionを使うとパフォーマンスが一番良い。

ソート順を保持する必要があるなら、SetでSortedSetを使用するか、Listを使う必要がある。Listは仕組み的に必ず関連を一度すべて取得しなければならないが、Setのほうが回避方法がある。

def "addToBooksでbookを保存する"() {
    setup:
    def author = Author.findByName("dummy")

    when:
    def book = new Book(title: "test")
    book.author = author
    book.save(flush: true)

    then:
    Author.withNewSession { Author.get(author.id).books*.title } as Set == ['hoge', 'foo', 'bar', 'test'] as Set
}

といった具合にauthor経由でカスケードさせるのではなく単にbookを保存すれば、余計なクエリーが走らないため、パフォーマンスが良い。Listは上記のように保存できない。

とそれぞれ特徴があるので、用途に合わせて使う必要がありますが、きちんと理解していないと、意図せず大量の関連を取得するSELECTや、順番を保持するために大量のUPDATEが走ることになるので注意が必要です。