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

日記はたしか朝6時に日付更新なので今書くと10日の日記になるんですが、ズルして11日分書いちゃお。
今回はManyToOneとOneToManyを使った双方向のリレーションシップです。

2.1.8.2 Bidirectional ManyToOne/OneToMany Relationships

例をベースに進めます。EmployeeとDepartmentのエンティティがあります。コードはこんなカンジ

@Entity(access = AccessType.FIELD)
public class Employee implements Serializable {
  @Id(generate = GeneratorType.AUTO)
  private int id;

  private String name;

  @ManyToOne
  private Department department;
  
  // getter, setter省略
}
@Entity(access = AccessType.FIELD)
public class Department implements Serializable {

  @Id(generate = GeneratorType.AUTO)
  private int id;

  private String name;

  @OneToMany(mappedBy="department")
  private Collection employees = new HashSet();
  
  // getter, setter省略
}

この例はリレーションシップに関して次のことをあらわしています。

  • エンティティEmployeeはエンティティDepartmentのひとつのインスタンスを参照する
  • エンティティDepartmentはエンティティEmployeeのコレクションを参照する
  • エンティティEmployeeはリレーションシップの所有側

次のマッピングDefaultが適用されます。

  • エンティティEmployeeはEMPLOYEEテーブルにマップされる。
  • エンティティDepartmentはDEPARTMENTテーブルにマップされる。
  • テーブルEMPLOYEEはテーブルDEPARTMENTに対する外部キーをもつ。外部キーの名前はDEPARTMENT_となる。すなわちこの例ではDEPARTMENT_ID。
  • 外部キーはDEPARTMENTのプライマリキーと同じ型をもつ。

双方向のOneToOneのときとほとんど同じですね。

エンティティをデプロイすると次のSQLが実行されます。

CREATE TABLE DEPARTMENT(ID SERIAL NOT NULL ,NAME VARCHAR, CONSTRAINT SYS_PK_DEPARTMENT PRIMARY KEY (ID) )
CREATE TABLE EMPLOYEE(ID SERIAL NOT NULL ,NAME VARCHAR,DEPARTMENT_ID INTEGER, CONSTRAINT SYS_PK_EMPLOYEE PRIMARY KEY (ID) )
CREATE UNIQUE INDEX SYS_PK_DEPARTMENT ON DEPARTMENT(ID)
CREATE UNIQUE INDEX SYS_PK_EMPLOYEE ON EMPLOYEE(ID)
CREATE INDEX SYS_IDX_9 ON EMPLOYEE(DEPARTMENT_ID)
ALTER TABLE EMPLOYEE ADD CONSTRAINT FK4AFD4ACE120989CF FOREIGN KEY (DEPARTMENT_ID) REFERENCES DEPARTMENT (ID)

マッピングDefault通りだと思います。


実際にエンティティを取得してみます。まずSQLでデータをいれます。どうでもいいんですけど、従業員の名前はうちのぬいぐるみや近所の犬の名前だったりします。

INSERT INTO DEPARTMENT (id, name) VALUES(1, '第1システム部');
INSERT INTO EMPLOYEE(id, name, department_id) VALUES(1, 'ゴン', 1)
INSERT INTO EMPLOYEE(id, name,  department_id) VALUES(2, 'うさはな', 1)
INSERT INTO EMPLOYEE(id, name,  department_id) VALUES(3, 'タクヤ', 1)

次のセッションBeanからアクセスしてみます。

@Stateless
@Remote(Session.class)
public class SessionBean implements Session{

  @PersistenceContext
  private EntityManager em;
  
  public void main() {
    
    // Departmentからたどる
    Department department = em.find(Department.class, 1);
    System.out.println("--Department--");
    System.out.println(department.getName());    
    System.out.println("--Employee--");
    for(Employee e: department.getEmployees()) {
      System.out.println(e.getName());
    }
    
    // Employeeからたどる
    Employee employee = em.find(Employee.class, 1);
    System.out.println("--Employee--");
    System.out.println(employee.getName());    
    System.out.println("--Department--");
    System.out.println(employee.getDepartment().getName());
  }
}

実行結果

02:14:21,917 INFO  [STDOUT] --Department--
02:14:21,917 INFO  [STDOUT] 第1システム部
02:14:21,917 INFO  [STDOUT] --Employee--
02:14:21,927 INFO  [STDOUT] ゴン
02:14:21,927 INFO  [STDOUT] うさはな
02:14:21,927 INFO  [STDOUT] タクヤ

02:14:21,927 INFO  [STDOUT] --Employee--
02:14:21,927 INFO  [STDOUT] ゴン
02:14:21,927 INFO  [STDOUT] --Department--
02:14:21,927 INFO  [STDOUT] 第1システム部

OK。DepartmentとEmployeeどちらからもたどれました。


実際に発行されたSQLは次のものでした。

2005-09-11 02:14:21,847 DEBUG [org.hibernate.SQL] select department0_.id as id24_
0_, department0_.name as name24_0_ from Department department0_ where department0
_.id=?
2005-09-11 02:14:21,917 DEBUG [org.hibernate.SQL] select employees0_.department_i
d as department3_1_, employees0_.id as id1_, employees0_.id as id25_0_, employees
0_.name as name25_0_, employees0_.department_id as department3_25_0_ from Employe
e employees0_ where employees0_.department_id=?

最初のSQLはDepartmentを取得したとき。次のSQLはDepartmentからEmployeeのコレクションを取得したときに実行されているみたいです。Lazy Loadingですね。Lazy Loadingが行われるのを忘れていて、最初JBossの外からDepartmentを取得してgetEmployeesメソッドを呼んだのですが見事にExceptionをくらいました。というわけで今回はセッションBean中でエンティティにアクセスしたのでした。

em.find(Employee.class, 1)としたときのSQLがないのですが、これはキャッシュから取得されたみたいです。なるほどねー。

前回と今回で双方向のリレーションシップをもつ例を動かして見ましたが、いまのところDefaultのマッピングルールは結構簡単かも。それよりも2.1.7節ででてきた双方向のリレーションシップに関するルールをちゃんと覚えないと書き方を迷ってしまいますね。その中でも書き方に迷わないようにするという意味では「many側が所有側で非所有側はmappedBy要素で所有側を参照する」というルールが大事かと思います。


2.1.8.2 Bidirectional ManyToOne/OneToMany Relationships終了です。