[Grails]GORMの動作(テーブル編)

# 参考：
http://grails.jp/doc/latest/guide/GORM.html

# 前提条件
- 以下のドメインクラスを用いる。
Author.groovy
Book.groovy

- デフォルトだとGrailsが生成するでversionカラムは利用しない。

- データベースはPostgreSQL9.3.1(for Windows)を用いた。

- データベース名はtest。

- パッケージ名はtestp

- dbCreate = "create-drop"

# 特に関連なし

```groovy:Author.groovy
class Author {

    String name

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

```groovy:Book.groovy
class Book {

    String title

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

**authorテーブル**

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|name|varchar(255)|not null|

**bookテーブル** 

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|title|varchar(255)|not null|

生成されるテーブルは以下。

```sql
test=# \d author
           Tabelle public.author
 Spalte |          Typ           | Attribute
--------+------------------------+-----------
 id     | bigint                 | not null
 name   | character varying(255) | not null
Indexe:
    "author_pkey" PRIMARY KEY, btree (id)

test=# \d book
            Tabelle public.book
 Spalte |          Typ           | Attribute
--------+------------------------+-----------
 id     | bigint                 | not null
 title  | character varying(255) | not null
Indexe:
    "book_pkey" PRIMARY KEY, btree (id)
```






# 別のドメインをプロパティに持つ

```groovy:Author.groovy
class Author {

    String name
    Book book
    
    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

```groovy:Book.groovy
class Book {

    String title

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

**authorテーブル**

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|book_id|bigint|not null|
|name|varchar(255)|not null|

**bookテーブル** 

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|title|varchar(255)|not null|

生成されるテーブルは以下。

```sql
test=# \d author
           Tabelle public.author
 Spalte  |          Typ           | Attribute
---------+------------------------+-----------
 id      | bigint                 | not null
 book_id | bigint                 | not null
 name    | character varying(255) | not null
Indexe:
    "author_pkey" PRIMARY KEY, btree (id)
Fremdschlüssel-Constraints:
    "fkac2d218b90583074" FOREIGN KEY (book_id) REFERENCES book(id)


test=# \d book
            Tabelle public.book
 Spalte |          Typ           | Attribute
--------+------------------------+-----------
 id     | bigint                 | not null
 title  | character varying(255) | not null
Indexe:
    "book_pkey" PRIMARY KEY, btree (id)
Fremdschlüsselverweise von:
    TABLE "author" CONSTRAINT "fkac2d218b90583074" FOREIGN KEY (book_id) REFEREN
CES book(id)
```



# 別のドメインにプロパティを持たれていて、そこに対してbelongsToを指定する。
上記と比べて相違は無し。

```groovy:Author.groovy
class Author {

    String name
    Book book

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

```groovy:Book.groovy
class Book {

    String title
    static belongsTo = [author: Author]

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

**authorテーブル**

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|book_id|bigint|not null|
|name|varchar(255)|not null|

**bookテーブル** 

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|title|varchar(255)|not null|

```sql
test=# \d author
           Tabelle public.author
 Spalte  |          Typ           | Attribute
---------+------------------------+-----------
 id      | bigint                 | not null
 book_id | bigint                 | not null
 name    | character varying(255) | not null
Indexe:
    "author_pkey" PRIMARY KEY, btree (id)
Fremdschlüssel-Constraints:
    "fkac2d218b90583074" FOREIGN KEY (book_id) REFERENCES book(id)


test=# \d book
            Tabelle public.book
 Spalte |          Typ           | Attribute
--------+------------------------+-----------
 id     | bigint                 | not null
 title  | character varying(255) | not null
Indexe:
    "book_pkey" PRIMARY KEY, btree (id)
Fremdschlüsselverweise von:
    TABLE "author" CONSTRAINT "fkac2d218b90583074" FOREIGN KEY (book_id) REFEREN
CES book(id)
```



# 別のドメインをhasManyでプロパティに持つ
結合用のテーブルが生成される。


```groovy:Author.groovy
class Author {

    String name
    static hasMany = [books: Book]

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

```groovy:Book.groovy
class Book {

    String title

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

**authorテーブル**

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|name|varchar(255)|not null|

**bookテーブル** 

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|title|varchar(255)|not null|

**author_bookテーブル** 

| 列 | 型 | 属性 |
|---|---|---|
|author_books_id |bigint|not null|
|book_id|bigint|not null|

生成されるテーブル

```sql
test=# \d author
           Tabelle public.author
 Spalte |          Typ           | Attribute
--------+------------------------+-----------
 id     | bigint                 | not null
 name   | character varying(255) | not null
Indexe:
    "author_pkey" PRIMARY KEY, btree (id)
Fremdschlüsselverweise von:
    TABLE "author_book" CONSTRAINT "fk2a7a111dafdcb969" FOREIGN KEY (author_book
s_id) REFERENCES author(id)


test=# \d book
            Tabelle public.book
 Spalte |          Typ           | Attribute
--------+------------------------+-----------
 id     | bigint                 | not null
 title  | character varying(255) | not null
Indexe:
    "book_pkey" PRIMARY KEY, btree (id)
Fremdschlüsselverweise von:
    TABLE "author_book" CONSTRAINT "fk2a7a111d90583074" FOREIGN KEY (book_id) RE
FERENCES book(id)


test=# \d author_book
     Tabelle public.author_book
     Spalte      |  Typ   | Attribute
-----------------+--------+-----------
 author_books_id | bigint |
 book_id         | bigint |
Fremdschlüssel-Constraints:
    "fk2a7a111d90583074" FOREIGN KEY (book_id) REFERENCES book(id)
    "fk2a7a111dafdcb969" FOREIGN KEY (author_books_id) REFERENCES author(id)
```



# 別のドメインからhasManyでプロパティに持たれていて、そこに対してbelongsToを指定する。
この場合は結合用のテーブルは生成されず、その代わりに従属しているドメイン（今回はBook）に従属されているドメイン（今回はAuthor）のIDが格納されるようになる。

```groovy:Author.groovy
class Author {

    String name
    static hasMany = [books: Book]

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

```groovy:Book.groovy
class Book {

    String title
    static belongsTo = [authors: Author]

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

**authorテーブル**

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|name|varchar(255)|not null|

**bookテーブル** 

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|authors_id |bigint|notnull|
|title|varchar(255)|not null|

```sql
test=# \d author
           Tabelle public.author
 Spalte |          Typ           | Attribute
--------+------------------------+-----------
 id     | bigint                 | not null
 name   | character varying(255) | not null
Indexe:
    "author_pkey" PRIMARY KEY, btree (id)
Fremdschlüsselverweise von:
    TABLE "book" CONSTRAINT "fk2e3ae9795e2d7" FOREIGN KEY (authors_id) REFERENCE
S author(id)


test=# \d book
              Tabelle public.book
   Spalte   |          Typ           | Attribute
------------+------------------------+-----------
 id         | bigint                 | not null
 authors_id | bigint                 | not null
 title      | character varying(255) | not null
Indexe:
    "book_pkey" PRIMARY KEY, btree (id)
Fremdschlüssel-Constraints:
    "fk2e3ae9795e2d7" FOREIGN KEY (authors_id) REFERENCES author(id)


```

# 1対1
ちょっと変わって、hasOneを使って1対1を実現すると、双方向のみサポートされる。
そのため、hasOneが付いていない（hasOneで指定される）ドメインのほうには、プロパティとして対応するドメインをもつ必要がある。

単純にプロパティにドメインを持つ場合との違いは、 **"author_book_id_key" UNIQUE CONSTRAINT, btree (book_id)** という制約が付与されること。
なお、hasOneで指定されると、指定されたテーブル（ドメイン）側に、その制約が付与される点に注意。

```groovy:Author.groovy
class Author {

    String name
    Book book

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

```groovy:Book.groovy
class Book {

    String title
    static hasOne = [author : Author]

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

**authorテーブル**

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|book_id|bigint|notnull|
|name|varchar(255)|not null|

**bookテーブル** 

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|title|varchar(255)|not null|


```
test=# \d author
           Tabelle public.author
 Spalte  |          Typ           | Attribute
---------+------------------------+-----------
 id      | bigint                 | not null
 book_id | bigint                 | not null
 name    | character varying(255) | not null
Indexe:
    "author_pkey" PRIMARY KEY, btree (id)
    "author_book_id_key" UNIQUE CONSTRAINT, btree (book_id)
Fremdschlüssel-Constraints:
    "fkac2d218b90583074" FOREIGN KEY (book_id) REFERENCES book(id)


test=# \d book
            Tabelle public.book
 Spalte |          Typ           | Attribute
--------+------------------------+-----------
 id     | bigint                 | not null
 title  | character varying(255) | not null
Indexe:
    "book_pkey" PRIMARY KEY, btree (id)
Fremdschlüsselverweise von:
    TABLE "author" CONSTRAINT "fkac2d218b90583074" FOREIGN KEY (book_id) REFEREN
CES book(id)
```



# 1対1（uniqueをつける）
通常の1対1に書いたとおり、hasOneで指定された側のテーブルの制約が変化するのであって、hasOneを書いたドメインに対応するテーブルの制約が変わるわけではない（ややこしい）
不公平だ！ということで、公式ドキュメントにも書いてあるとおり、hasOneを記述するドメインにunique制約をつけてあげる。
データベース上は変化無し。

```groovy:Author.groovy
class Author {

    String name
    Book book

    static constraints = {
    }
    static mapping = {
        version false
    }
}
```

```groovy:Book.groovy
class Book {

    String title
    static hasOne = [author : Author]

    static constraints = {
        author unique: true
    }
    static mapping = {
        version false
    }
}
```

**authorテーブル**

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|book_id|bigint|notnull|
|name|varchar(255)|not null|

**bookテーブル** 

| 列 | 型 | 属性 |
|---|---|---|
|id|bigint|not null|
|title|varchar(255)|not null|


```
test=# \d author
           Tabelle public.author
 Spalte  |          Typ           | Attribute
---------+------------------------+-----------
 id      | bigint                 | not null
 book_id | bigint                 | not null
 name    | character varying(255) | not null
Indexe:
    "author_pkey" PRIMARY KEY, btree (id)
    "author_book_id_key" UNIQUE CONSTRAINT, btree (book_id)
Fremdschlüssel-Constraints:
    "fkac2d218b90583074" FOREIGN KEY (book_id) REFERENCES book(id)


test=# \d book
            Tabelle public.book
 Spalte |          Typ           | Attribute
--------+------------------------+-----------
 id     | bigint                 | not null
 title  | character varying(255) | not null
Indexe:
    "book_pkey" PRIMARY KEY, btree (id)
Fremdschlüsselverweise von:
    TABLE "author" CONSTRAINT "fkac2d218b90583074" FOREIGN KEY (book_id) REFEREN
CES book(id)
```

ちなみに、この状態で以下のようなコードを試してみると、ともにデータが保存されない。

```groovy
groovy:000> new Book(title:"a").save(flush:true)
===> null
groovy:000> new Author(name:"author_a").save(flush:true)
===> null
```

保存するときは、Bookドメインを基準に保存しないといけない

```groovy
groovy:000> import testp.*
===> [import testp.*]
groovy:000> book1 = new Book(title:"book1")
===> testp.Book : (unsaved)
groovy:000> author1 = new Author(name:"author1")
===> testp.Author : (unsaved)
groovy:000> book1.author = author1
===> testp.Author : (unsaved)
groovy:000> book1.save(flush:true)
===> testp.Book : 1
groovy:000>
```

逆に、Authorドメインを基準に保存しようとすると例外（NullPointerException)が発生する。

```groovy
groovy:000> book2 = new Book(title:"book2")
===> testp.Book : (unsaved)
groovy:000> author2 = new Author(name:"author2")
===> testp.Author : (unsaved)
groovy:000> author2.book = book2
===> testp.Book : (unsaved)
groovy:000> author2.save(flush:true)
2014-01-27 16:25:18,761 [main] ERROR util.JDBCExceptionReporter  - FEHLER: NULL-
Wert in Spalte ÔÇ×book_idÔÇ£ verletzt Not-Null-Constraint
  Detail: Fehlgeschlagene Zeile enth├ñlt (3, null, author2).

ERROR org.springframework.dao.DataIntegrityViolationException:
could not insert: [testp.Author]; SQL [insert into author (id, book_id, na
me) values (nextval ('hibernate_sequence'), ?, ?)]; constraint [null]; nested ex
ception is org.hibernate.exception.ConstraintViolationException: could not inser
t: [testp.Author]
        at org.springframework.orm.hibernate3.SessionFactoryUtils.convertHiberna
teAccessException (SessionFactoryUtils.java:643)
        at org.springframework.orm.hibernate3.HibernateAccessor.convertHibernate
AccessException (HibernateAccessor.java:412)
        at org.springframework.orm.hibernate3.HibernateTemplate.doExecute (Hiber
nateTemplate.java:411)
        at org.springframework.orm.hibernate3.HibernateTemplate.execute (Hiberna
teTemplate.java:339)
        at testp.Author.save (Author.groovy)
        at testp.Author$save.call (Unknown Source)
```

# メモ

単純にプロパティとしてドメインを持つと、そのクラス(ドメインを持った側)に外部キーがセットされる。
hasManyを使うと、専用テーブルを生成して、そこで関連を管理する。
ただし！hasManyの場合、hasManyキーワードで指定された側のクラスにbelongsToを付けると、関連を管理する専用テーブルを生成・利用せずに、
belongsToを書いている側のドメイン（テーブル）が、親側（hasManyキーワードを書いている側
）のidを外部キーとして保持するようになる。