Powered by SmartDoc

Relation Mapping (2) 多対一の関係・一対多の関係

多対一

多対一の関係

図10.1[多対一の関係]は、多対一の関係を表したものです。

SQLのCREATE TABLE文も見てみましょう。

CREATE TABLE student (
	id	int,
	name VARCHAR(50),
	CONSTRAINT pk_student PRIMARY KEY(id)
);

CREATE TABLE mail_address (
	student_id int,
	address VARCHAR(50),
	CONSTRAINT pk_mail_address PRIMARY KEY(address),
	CONSTRAINT fk_mail_address_student_id FOREIGN KEY(student_id) REFERENCES \
    student(id)
);

この例では「メールアドレステーブル」の複数の行が、「学生テーブル」のひとつの行と関連しています。つまり、メールアドレステーブルから見ると、学生テーブルとは多対一の関係になるわけです。

Student.java
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.Column;

@Entity
@Table(name = "student")
public class Student implements Serializable {

	private int id;
	private String name;
   
	@Id
	@Column(name="id")
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	@Column(name="name")
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

studentテーブルの定義に則り、対応するプロパティを用意しているだけですね。

続いて、MailAddress.javaです。

MailAddress.java
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.Column;
import javax.persistence.ManyToOne;
import javax.persistence.JoinColumn;
import javax.persistence.CascadeType;
import javax.persistence.FetchType;

@Entity
@Table(name = "mail_address")
public class MailAddress implements Serializable {

   private String address;
   private Student owner;

   @Id
   @Column(name="address")
   public String getAddress() {
      return address;
   }

   public void setAddress(String address) {
      this.address = address;
   }

   @ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
   @JoinColumn(name="student_id", referencedColumnName="id")
   public Student getOwner() {
      return owner;
   }

   public void setOwner(Student owner) {
      this.owner = owner;
   }
}

ownerプロパティに、@ManyToOne Annotationと、@JoinColumn Annotationを付加しています。

   @ManyToOne(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
   @JoinColumn(name="student_id", referencedColumnName="id")
   public Student getOwner() {
      return owner;
   }

@JoinColumn Annotationでは、mail_addressテーブルのstudent_id項目と、studentテーブルのid項目がジョインされていることを示しています。

@ManyToOne Annotationには、メンバが2つあります。1つめのメンバは、カスケード処理についての記述です。Java Persistence APIでは、テーブル間の関連があるときに、処理を伝播させる(カスケードさせる)ことが可能になります。CascadeTypeというenumでは、次の値が定義されています。

public enum CascadeType{
  ALL, PERSIST, MERGE, REMOVE, REFRESH
};

2つめのメンバは、関連インスタンスのロードのタイミングを指定しています。2種類の値のいずれかを指定できます。「即時(eager)ロード」は元のオブジェクトがロードされると、関連するオブジェクトも同時にロードされるしくみです。「遅延(lazy)ロード」は、関連するオブジェクトがアクセスされたときにロードされるしくみです。

@OneToOneと@ManyToOneのデフォルトはEAGERになっています。また、@OneToManyと@ManyToManyのデフォルトはLAZYです。*ToManyというスタイルのAnnotationは、ロードするオブジェクトが多数になることが想定されるため、このようなつくりになっています。

最後に、メールアドレスの情報から、持ち主の学生の情報を取得するSession Facadeです。

public  Student  findStudentByAddress (String address)  {
      MailAddress mailAddress = 
	  	(MailAddress)em.find(MailAddress.class, address);
      return  mailAddress.getOwner();
   }

一対多

一対多の関係

一対多の関係(図10.2[一対多の関係])は、多対一の逆パターンになります。

先ほどのサンプルでは、「メールアドレス」から「学生」を見れば多対一ですが、「学生」から「メールアドレス」を見れば一対多の関係になりますね。

まずは、先ほど出てきたStudent.javaです。「学生」から「メールアドレス」を見るといっても、このクラスにはメールアドレスの情報はありません。どうすればよいのでしょうか?

Student.java
import java.io.Serializable;
import javax.persistence.Entity;
import javax.persistence.Table;
import javax.persistence.Id;
import javax.persistence.Column;

@Entity
@Table(name = "student")
public class Student implements Serializable {

	private int id;
	private String name;
   
	@Id
	@Column(name="id")
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	@Column(name="name")
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
}

もともと、一対多の関係というのは、テーブルの第一正規化以降の正規化の過程により排除されるのが普通です。よって、学生のテーブルにはメールアドレスの情報は載りません。

そのため、@OneToMany Annotationを用いて、もともと存在していないmailAddressesプロパティを用意しなければならないのです。

    @OneToMany(cascade=CascadeType.ALL)
    @JoinColumn(name=“student_id", 
                              referencedColumnName=“id")
    public Collection<MailAddress> 
                                 getMailAddresses() {
                 return mailAddresses; 
    }

では、学生の情報からメールアドレス一覧を得るSession Facade(の一部)です。

public Collection<MailAddress>     
                     findMailAddressesById(int id) {
	Student student =
		(Student)em.find(Student.class, id);
	return student.getMailAddresses();
}

多対多

多対多の関係

多対多は、図10.3[多対多の関係]に見るような関係になります。学生と履修科目の関係は、1人の学生は複数の科目を履修し、ひとつの科目は複数の学生によって履修されることになります。よって、この関係は多対多となります。

多対多の関係では、学生と科目の関係を表す中間テーブルが必要になります。具体的には、誰がどの科目をとっているのか、あるいはどの科目は誰にとられているのか一覧できるようにします(図10.4[中間テーブル])。

中間テーブル

関連するテーブル定義も見ておきましょう。

CREATE TABLE student (
	id	int,
	name VARCHAR(50),
	CONSTRAINT pk_student PRIMARY KEY(id)
);

CREATE TABLE course (
	id	int,
	course_name VARCHAR(50),
	teacher_name VARCHAR(50),
	CONSTRAINT pk_course_id PRIMARY KEY(id)
);

CREATE TABLE course_regist (
	student_id int,
	course_id int,
	CONSTRAINT fk_course_regist_student_id FOREIGN KEY(student_id) REFERENCES \
    student(id),
	CONSTRAINT fk_course_regist_course_id FOREIGN KEY(course_id) REFERENCES \
    course(id)
);

中間テーブルを介した構造は、図10.5[中間テーブルを介した構造]のようになっています。

中間テーブルを介した構造

中間テーブルは、Entity Beanにする必要はありません。ここでは、Student.javaに@AssociationTable Annotationを記述して、中間テーブルの情報を記載しています。

 @ManyToMany(cascade=CascadeType.ALL)
 @AssociationTable(
	table=@Table(name=“course_regist"),
        	joinColumns=@JoinColumn(
			name=“Student_ID",
                            referencedColumnName="ID"),
        	inverseJoinColumns=@JoinColumn(
                       	name=“Course_ID",
                         referencedColumnName="ID")
   )
   public Collection<Course> getCourses() {…}