リレーションシップ
データベースを利用する場合、いくつかのテーブルを関連付けて利用することもあります。SQLでは「JOIN」というものを使い、複数のテーブルを結合して扱うことができました。こうした複数テーブルの関連付けは、Grailsでは「リレーションシップ」と呼ばれています。
リレーションシップの設定は非常に簡単です。ドメインクラスに、そのためのstaticなプロパティを用意しておくだけです。リレーションシップの種類と、用意されているstatic
プロパティの関係を以下に整理しましょう。
1対多対応「hasMany」
あるテーブルのレコード1つに対し、別のテーブルのレコードが複数対応するという形の結合です。これは、1対多の「1」の側(主テーブル)に「hasMany」を用意し、「多」の側(従テーブル)のテーブルを関連付けます。
hasManyは、両者の間に密接なつながりが構築されます。例えば、hasManyを用意しているテーブル(主テーブル)のレコードを削除すると、もう1方の従テーブルからも、そのレコードに関連付けられたレコードがすべて削除されます。
多対1対応「belongsTo」
あるテーブルの複数のレコードに対し、別のテーブルのレコード1つが対応するという形の結合です。これは、先ほどのhasManyを逆に設定すれば同じような働きをすることは可能なのですが、hasManyは非常に密接な関連付けがされ、主のテーブルのレコードを削除すると従側も削除されます。
このbelongsToは、多対1の「多」の側(従テーブル)に配置します。これにより、そのテーブルが、もう1方のテーブルに従属するものとして機能するようになります。従属する側に設定されますから、こちらのテーブルのレコードを削除しても、もう片方のテーブルのレコードは削除されません。
1対1対応
常にあるテーブルの1レコードが別のテーブルの1レコードに対応するという結合です。これは、1対多対応の「hasMany」をそのまま応用してできます。対応する複数のレコードから1つだけをピックアップするように考えればよいのです。もし「完全に、常に1対1でしかレコードが存在できないようにしたい」ということであれば、従属するテーブル側に用意する「結合する主テーブルのフィールド」(後述のCommentsドメインクラスの項目を参照ください)の値をユニークであるようにバリデーション設定し、同じものに結合するレコードが作れないようにすればよいでしょう。
多対多対応
あるテーブルの複数レコードに対し、別のテーブルの複数レコードが対応するという形の結合です。これは、「hasMany」と「belongsTo」を組み合わせることで可能です。両方のテーブルに、1対多対応の「hasMany」を用意し、さらにどちらかに「belongsTo」を用意するのです。hasManyだけでは、どちらが主テーブルでありどちらがそれに従属するものかが分かりません。belongsToを用意することで、両者の主従関係が明確になります。
Commentsドメインクラスの作成
では、実際にテーブルの結合がどのように行われるのか試してみましょう。ここでは、Boardsに投稿された記事につけられるコメントを管理する「Comments」テーブルを用意することにしましょう。これは、ざっと次のような項目をもちます。
項目 | 説明 |
ID | 整数値。プライマリキーとして用意します。 |
BOARD | 関連するBoardsのインスタンスを保管します。 |
NAME | 文字列。投稿者名を保管します。 |
MESSAGE | 投稿したコメントを保管します。 |
リレーションシップで2つのテーブルを関連付ける場合、従属するテーブル側に、結合する主テーブルのレコードを保管するためのフィールドを用意します。ここでは、BoardsにCommentsを関連付けますので、このようにComments側に、関連付けられるBoardsオブジェクトを保管するフィールドを用意しておくわけです。
では、実際にCommentsを作成しましょう。ここでのサンプルでは、データベースプログラムにHSQLDBを利用していますから、特にテーブルの準備などは不要です。ドメインクラスを作成するだけです。
class Comments { Long id Boards board // 結合されるBoardsを保管する String name String message static belongsTo = Boards static constraints = { message(blank:false) } }
Commentsは、Boardsに従属しますので、belongsToを用意しておきます。また、constraintsを使い、messageは必須項目に設定しておきましょう。idはプライマリキーとして自動設定されますからconstraintsに用意する必要はありません。またboardも、後述しますがhasManyで強い関連付けがされる場合、自動的に必須項目として扱われますのでconstraintsは不要です。
Boardsドメインクラスの修正
では、Boardsドメインクラス側も修正しましょう。こちらは、hasManyを追加するだけです。
class Boards { Long id String name String title String content static hasMany = [comments:Comments] //★追加 static constraints = { name(blank:false) title(blank:false) } }
ここでは、hasManyの値として[comments:Comments]という値が設定されていますが、これはCommentsを「comments」という名前で連携させる、ということを意味します。これにより、結合されて取り出されたCommentsのレコードは、Boardsのcommentsとして得られるようになります。
先のbelongsToでは、単に名前を文字列で指定するだけでした。こちらは、結合された主テーブルのレコードをboardとして保管していますから、別途変数などを用意する必要もなく、ただ「どのドメインクラスと関連があるのか」さえ指定すればよかったのです。が、hasManyの設定では、関連するCommentsを保管する用意などはありませんから、このように「プロパティ名:結合するドメインクラス名」という形で記述をします。