Play Framework(Java)でEbeanを使ってリレーションを持つテーブルを操作する
前回、EBeanを使ったデータベースの操作を試してみましたが、Ebeanには複数のModelを関連付けて処理する仕組みも用意されています。Modelクラスのフィールドにアノテーションを付けることで、1対1、1対多、多対多といった関係付けを行うことができます。今回は、Ebeanを使ってリレーションを持つテーブルの操作について試してみました。
構成
- Windows 8.1
- Java 7
- Play Framework 2.2.3
Modelの連携の種類について
Modelの連携には以下の種類があります。
- OneToOne(1対1)
- OneToMany(1対多)、ManyToOne(多対1)
- ManyToMany(多対多)
サンプル用のModelについて
それぞれの連携を説明するために、今回以下の2つのModelを用意しました。
- Message・・・メッセージを管理
- Member・・・ユーザ名を管理
まずはmodels パッケージに,以下のようにMessage.javaとMember.javaを作成します。
Message.java
package models;
import java.util.*;
import javax.persistence.*;
import javax.validation.*;
import com.avaje.ebean.*;
import play.db.ebean.*;
import play.data.validation.*;
@Entity
public class Message extends Model {
@Id
public Long id;
@Required
public String name;
@Required
public String message;
@CreatedTimestamp
public Date postdate;
public static Finder<Long, Message> find =
new Finder<Long, Message>(Long.class, Message.class);
}
Member.java
package models;
import java.util.*;
import javax.persistence.*;
import javax.validation.*;
import com.avaje.ebean.*;
import play.db.ebean.*;
import play.data.validation.*;
@Entity
public class Member extends Model {
@Id
public Long id;
@Required
public String name;
@CreatedTimestamp
public Date postdate;
public static Finder<Long, Member> find =
new Finder<Long, Member>(Long.class, Member.class);
}
OneToOne
OneToOneは、あるModelのエンティティ1つに対して、別のModelのエンティティ1つが対応する関係です。
Message.java
Messageインスタンスに1つのMemberインスタンスが関連するようにするには、Message.javaに以下の内容を追加します。@OneToOneというアノテーションが、OneToOneの関係を指定するものになります。
@OneToOne(cascade = CascadeType.ALL) public Member member;
エンティティと関連付けられたエンティティに対する処理を自動で行う場合はCascadeTypeを指定します。cascade = CascadeType.ALLを追加することで、参照や更新などのすべての操作に対して処理を自動で行うことができるようになります。この設定がない場合は、操作ごとにApplication.javaに処理を書く必要があります。
Member.java
ユーザ名からMemberインスタンスを取得できるように、Member.javaにfindByIdメソッドを追加します。
public static Member findById(String input){
return Member.find.where().eq("id", input).findList().get(0);
}
Application.java
Application.javaに以下のようにfindByIdを追加します。findByIdでMemberインスタンスを取得し、saveによりMessageインスタンスに保存するだけで関連付けをすることができます。
public static Result create() {
Form<Message> f = new Form(Message.class).bindFromRequest();
if (!f.hasErrors()) {
Message data = f.get();
data.member = Member.findById(data.name);
data.save();
return redirect("/");
} else {
return badRequest(add.render("ERROR",f));
}
}Evolutionを実行すると、以下のDLLが自動的に作成されます。Message.javaに追加したmemberがmember_idとして作成されていることが分かります。
create table message ( id bigint not null, name varchar(255), message varchar(255), member_id bigint, postdate timestamp not null, constraint pk_message primary key (id)) ;
OneToMany、ManyToOne
OneToManyは、あるModelのエンティティ1つに対して、別のModelの複数のエンティティが対応する関係です。ManyToOneは、逆にあるModelの複数のエンティティが、別のModelのエンティティ1つが対応します。
Memberインスタンスに複数のMessageインスタンスが関連するようにするには、Message.javaとMember.javaに以下の内容を追加します。@OneToMany、@ManyToOneというアノテーションが、OneToMany、ManyToOneの関係を指定するものになります。
Message.java
@OneToMany public Member members;
Member.java
@ManyToOne(cascade = CascadeType.ALL)
public List<Message> messages;
public static Member findById(String input){
return Member.find.where().eq("id", input).findList().get(0);こちらでも、エンティティと関連付けられたエンティティに対する処理を自動で行うため、cascade = CascadeType.ALLを指定しておきます。
Application.java
Application.javaに以下のようにfindByIdを追加します。findByIdでMemberインスタンスを取得し、saveによりMessageインスタンスに保存するだけで関連付けをすることができます。
Messageインスタンスの操作しかしていませんが、Memberインスタンスも自動的に反映されるため処理を追加する必要はありません。
public static Result create() {
Form<Message> f = new Form(Message.class).bindFromRequest();
if (!f.hasErrors()) {
Message data = f.get();
data.member = Member.findById(data.name);
data.save();
return redirect("/");
} else {
return badRequest(add.render("ERROR",f));
}
}Evolutionを実行すると、DLLが自動的に作成されますが、OneToManyやManyToOneでは変化はありません。
ManyToMany
ManyToManyは、あるModelのの複数のエンティティに対して、別のModelの複数のエンティティが対応する関係です。
複数のMemberインスタンスに複数のMessageインスタンスが関連するようにするには、Message.javaとMember.javaに以下の内容を追加します。@ManyToManyというアノテーションが、ManyToManyの関係を指定するものになります。Message.javaには、複数のMemberインスタンスを格納するために、配列を追加しました。
Message.java
public List<String> name; @ManyToMany(cascade = CascadeType.ALL) public List<Member> members;
Member.java
@ManyToMany(mappedBy="members", cascade = CascadeType.ALL)
public List<Message> messages;
public static Member findById(String input){
return Member.find.where().eq("id", input).findList().get(0);こちらでも、エンティティと関連付けられたエンティティに対する処理を自動で行うため、cascade = CascadeType.ALLを指定しておきます。
また、ManyToManyではエンティティの主従関係を明示的にするためmappedByを指定しています。mappedBy="members"は、Messageが主、Memberが従であることを示しています。また、mappedByを指定しない場合、エンティティと関連付けした状態でインスタンスを削除をすると参照整合性制約違反が発生するため、自動的に処理をするには指定したほうがいいかもしれません。
Application.java
MemberのIDの配列を取得し、それらのIDからfindByIdでMemberインスタンスを取得しています。取得した複数のMemberインスタンスをmembersにaddにより追加します。saveによりMessageインスタンスに保存し関連付けをすることができます。Messageインスタンスの操作しかしていませんが、Memberインスタンスも自動的に反映されるため処理を追加する必要はありません。
public static Result create() {
Form<Message> f = new Form(Message.class).bindFromRequest();
if (!f.hasErrors()) {
Message data = f.get();
List<String> names = data.name;
for (String nm : names){
Member m = Member.findById(nm);
data.members.add(m);
}
data.save();
return redirect("/");
} else {
return badRequest(add.render("ERROR",f));
}
}Evolutionを実行すると、以下のDLLが自動的に作成されます。MessageとMemberの関係テーブルがmessage_memberとして作成されていることが分かります。
create table message_member ( message_id bigint not null, member_id bigint not null, constraint pk_message_member primary key (message_id, member_id)) ;