JPAによるリレーション: @OneToManyと@ManyToOne

ユーザ認証ができたので、これまでに作った掲示板を改良してみます。名前とコメントが入力できるようになっていますが、名前をCommentのフィールドにするのではなく、CommentとUserを関連付けます。

ユーザはコメントを複数書くことができるので、UserとCommentは1対多の関係となります。ユーザごとにコメント表示できるようにし、またコメントがどのユーザのものかを表示できるようにするため、双方向の関係を持つようにします。

このような関係(リレーション)を定義するにはEntityにアノテーションをつけます。

Entity

まず、UserにCommentを格納するフィールドを追加します。Userが1でCommentが多と定義するには@OneToManyをつけます。

  • 30行目: @OneToManyアノテーションで「1対多」であることを表す。アノテーションの引数であるmappedByについては後述。
  • 31行目: 「多」となるEntityをListで格納できるようにする。

Commentは「多」なので@ManyToOneアノテーションをつけます。

  • 21行目: @MaynToOneアノテーションで「多対1」であることを表す。
  • 22行目: 「1」となるEntityを格納できるようにする。

それぞれのEntityにアノテーションを付けたので双方向の関係(UserからCommentを、CommentからUserを取得できる)になります。

これに合わせてControllerとViewも修正します。

Controller

Controllerでは、コメントのPOST時にUserを取得し、Commentにセットしてから保存します。

View

Viewでは、名前の入力欄を削除し、表示される名前はUserのusernameに変更します。

  • 10行目: commentからuserを取得しそのフィールドであるusernameを表示する。
  • 13行目: 名前の入力欄は削除し、textの入力欄だけにする。

これで、UserとCommentを関連付けられました。

データベースにはどのように保存されているのか

@OneToManyと@ManyToOneをつかって双方向の1対多のリレーションを設定した場合、RDMSではどのように保存されているのでしょうか。 H2コンソールを使って中身を覗いてみましょう。

Userが保存されるUSERSテーブルは次のようになります。

Dbuser

UserにcommentListは見当たりません。

Commentが保存されるCOMMENTテーブルは次のようになります。

Dbcomment

UserのIDが格納されるカラムができています。

このようなテーブルになっているのは@OneToManyにmappedByがついているためです。

通常、@OneToManyをつけた場合は、Listを格納するために専用のテーブル(この場合、UserのIDとCommentのIDの対応が保存されるテーブル)が作成されます。@ManyToOneの場合は上記のようにテーブルにカラムが追加されます。これだと情報を二重にもってしまうことになります。また、UserにCommentのListを保存してsaveする必要があります。これでは、コードも煩雑になり、コードにミスがあればデータベースに不整合がおこりかねません。

これを防ぐために、@OneToManyと@ManyToOneの双方向のリレーションを設定する場合は、@OneToManyがつけられたEntityにアノテーションの引数でmappedByを指定し、参照するデータをもう一方の@ManyToOneがつけられたEntityのものにします。

今回の場合は、UserのcommentListは(UserのIDとCommentのIDの対応が保存されるテーブルからデータを取得するのではなく)CommentのUserフィールドの情報から取得していることになります。