参考:
前提条件
-
以下のドメインクラスを用いる。
Author.groovy
Book.groovy -
デフォルトだとGrailsが生成するでversionカラムは利用しない。
-
データベースはPostgreSQL9.3.1(for Windows)を用いた。
-
データベース名はtest。
-
パッケージ名はtestp
-
dbCreate = "create-drop"
特に関連なし
class Author {
String name
static constraints = {
}
static mapping = {
version false
}
}
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 |
生成されるテーブルは以下。
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)
別のドメインをプロパティに持つ
class Author {
String name
Book book
static constraints = {
}
static mapping = {
version false
}
}
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 |
生成されるテーブルは以下。
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を指定する。
上記と比べて相違は無し。
class Author {
String name
Book book
static constraints = {
}
static mapping = {
version false
}
}
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 |
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でプロパティに持つ
結合用のテーブルが生成される。
class Author {
String name
static hasMany = [books: Book]
static constraints = {
}
static mapping = {
version false
}
}
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 |
生成されるテーブル
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が格納されるようになる。
class Author {
String name
static hasMany = [books: Book]
static constraints = {
}
static mapping = {
version false
}
}
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 |
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で指定されると、指定されたテーブル(ドメイン)側に、その制約が付与される点に注意。
class Author {
String name
Book book
static constraints = {
}
static mapping = {
version false
}
}
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制約をつけてあげる。
データベース上は変化無し。
class Author {
String name
Book book
static constraints = {
}
static mapping = {
version false
}
}
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:000> new Book(title:"a").save(flush:true)
===> null
groovy:000> new Author(name:"author_a").save(flush:true)
===> null
保存するときは、Bookドメインを基準に保存しないといけない
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: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を外部キーとして保持するようになる。