EJB 3.0(Public Draft)入門記 Java Persistence API Chapter2 その9

Chapter2ももうその9になってしましました。でもその割りにはすすんでいないような。finalが出るまでにPublic Draftの最後までいきたいなぁ。

2.1.8.4 Bidirectional ManyToMany Relationships

今回は双方向のManyToManyなリレーションシップについてです。
例をベースに進めます。次のようなProjectエンティティとEmployeeエンティティがあります。前回までは@Id(generate=GeneratorType.AUTO)とgenerate要素にGeneratorType.AUTOを指定してましたが取得するだけなら関係ないのではずしました。

@Entity(access=AccessType.FIELD) 
public class Project {
  
  @Id
  private int id;
  
  private String name;
  
  @ManyToMany
  private Collection employees;
  
  //getter,setter省略
}
@Entity(access = AccessType.FIELD)
public class Employee {

  @Id
  private int id;

  private String name;

  @ManyToMany(mappedBy="employees")
  private Collection projects;

  // getter,setter省略
}

エンティティ間の関係はつぎのとおりです。

  • エンティティProjectはエンティティEmployeeのコレクションを参照する
  • エンティティEmployeeはエンティティProjectのコレクションを参照する
  • エンティティProjectがリレーションシップの所有側となる

このとき次のマッピングDefaultが適用されるそうです。

  • エンティティProjectはテーブルPROJECTにマップされる。
  • エンティティEmployeeはテーブルEMPLOYEEにマップされる。
  • PROJECT_EMPLOYEE(所有側の名前が先)という関連テーブルが必要。この関連テーブルは次の2つの外部キーをもつ。
    • ひとつはテーブルPROJECTを参照するもので、型はPROJECTのプライマリキーと一緒。外部キーのカラム名は「PROJECTS_」。はPROJECTテーブルのプライマリキーのカラムをあらわしている。つまりこの例では外部キーのカラム名は「PROJECTS_ID」となる。
    • もうひとつはテーブルEMPLOYEEを参照するもので、型はEMPLOYEEのプライマリキーと一緒。外部キーのカラム名は「EMPLOYEES_」。はEMPLOYEEテーブルのプライマリキーのカラムをあらわしている。つまりこの例では外部キーのカラム名は「EMPLOYEES_ID」となる。

Embeddable EJB 3を動かすとhbm2ddlが次のDDLでテーブルを作ってくれました。上記のマッピングDefaultにしたがっていることがわかります。

CREATE TABLE EMPLOYEE(ID INTEGER NOT NULL PRIMARY KEY,NAME VARCHAR(255))
CREATE TABLE PROJECT(ID INTEGER NOT NULL PRIMARY KEY,NAME VARCHAR(255))
CREATE TABLE PROJECT_EMPLOYEE(PROJECTS_ID INTEGER NOT NULL,EMPLOYEES_ID INTEGER NOT NULL,
 CONSTRAINT FKD1E6E154CD4FF0E4 FOREIGN KEY(PROJECTS_ID) REFERENCES PROJECT(ID),
 CONSTRAINT FKD1E6E154BF508158 FOREIGN KEY(EMPLOYEES_ID) REFERENCES EMPLOYEE(ID))

関連テーブルの外部キーのカラム名ですが、PROJECTS_IDとかEMPLOYEES_IDとか複数形になるのですね(エンティティクラスのフィールド名が使われているからなんですが)。ちょっと違和感。
あと、例ではProjectがリレーションシップの所有側ですが、所有側をProjectではなくEmployeeにしても関連テーブルの名前がEMPLOYEE_PROJECTになるだけでどっちでもいいのかもしれないです。


実際に動かしてみます。
まずデータを作ります。いつもながら適当なデータ...。

INSERT INTO EMPLOYEE VALUES(1,'ゴン');
INSERT INTO EMPLOYEE VALUES(2,'うさはな');
INSERT INTO EMPLOYEE VALUES(3, 'タクヤ');
INSERT INTO PROJECT VALUES(1, 'Javaプロジェクト');
INSERT INTO PROJECT VALUES(2, 'dot Netプロジェクト');
INSERT INTO PROJECT_EMPLOYEE VALUES(1,  1);
INSERT INTO PROJECT_EMPLOYEE VALUES(1,  2);
INSERT INTO PROJECT_EMPLOYEE VALUES(1,  3);
INSERT INTO PROJECT_EMPLOYEE VALUES(2,  1);
INSERT INTO PROJECT_EMPLOYEE VALUES(2,  2);

次のプログラムを使ってエンティティを取得してみます。

@Stateless
public class ClientBean implements Client {

  @PersistenceContext
  private EntityManager em;

  public void main() {
    // Projectからたどる
    System.out.println("** Projectからたどる **");
    Project project = em.find(Project.class, 1);
    System.out.println(project.getName());
    for (Employee e : project.getEmployees()) {
      System.out.println(" " + e.getName());
    }
    
    // Employeeからたどる
    System.out.println("** Employeeからたどる **");
    Employee employee = em.find(Employee.class, 1);
    System.out.println(employee.getName());
    for (Project p : employee.getProjects()) {
      System.out.println(" " + p.getName());
    }
  }

  public static void main(String[] args) throws Exception {
    EJB3StandaloneBootstrap.boot(null);
    EJB3StandaloneBootstrap.deployXmlResource("ejb3-deployment.xml");

    InitialContext ctx = new InitialContext();
    Client c = (Client) ctx.lookup(Client.class.getName());
    c.main();

    EJB3StandaloneBootstrap.shutdown();
  }
}

実行結果(SQLのログ出力含む)です。

 ** Projectからたどる **
Hibernate: select project0_.id as id1_0_, project0_.name as name1_0_ from Project project0_ where project0_.id=?
Javaプロジェクト
Hibernate: select employees0_.projects_id as projects1_0_, employees0_.employees_id as employees2_0_ 
from Project_Employee employees0_ where employees0_.projects_id=?
Hibernate: select employee0_.id as id0_0_, employee0_.name as name0_0_ from Employee employee0_ where employee0_.id=?
 ゴン
Hibernate: select employee0_.id as id0_0_, employee0_.name as name0_0_ from Employee employee0_ where employee0_.id=?
 うさはな
Hibernate: select employee0_.id as id0_0_, employee0_.name as name0_0_ from Employee employee0_ where employee0_.id=?
 タクヤ
 
 ** Employeeからたどる **
ゴン
Hibernate: select projects0_.employees_id as employees2_0_, projects0_.projects_id as projects1_0_ 
from Project_Employee projects0_ where projects0_.employees_id=?
 Javaプロジェクト
Hibernate: select project0_.id as id1_0_, project0_.name as name1_0_ from Project project0_ where project0_.id=?
 dot Netプロジェクト

ProjectからもEmployeeからもたどれました。思いっきりLazy Loadingされてる模様。実行されたSQLから察するにEmployeeからたどった場合はProjectからたどったときにキャッシュされた値(ゴン、Javaプロジェクト)が使われているようです。