EJB 3.0(Public Draft)入門記 Java Persistence API Chapter3 その2

リレーションシップのとこよりライフサイクルのとこのほうが難しいかもと思い始めてきました。


3.2 Entity Instance's Life Cycle

この節ではエンティティのライフサイクルを管理するEntityManagerの操作が説明されるそうです。

エンティティのライフサイクルには次の4つがあります。

  • 新規(new)
    • 新しく生成されたエンティティのインスタンスは永続IDをもたずpersistence contextに関連づいていない。
  • 管理された(managed)
    • 管理されたエンティティのインスタンスは現在のpersistence contextに関連付けられた永続IDをもつ。
  • 切り離された(detached)
    • 切り離されたエンティティのインスタンスはpersistence contextに関連付けられていない永続IDをもつ。
  • 削除された(removed)
    • 削除されたエンティティのインスタンスは永続IDをもち、persistence contextに関連付けられている。削除されたエンティティのインスタンスはDBから削除されることが予定される。

cascadeアノテーションを使うと、あるエンティティへの操作で生じたライフサイクルの変化が関連エンティティに伝播するらしいです。


3.2.1 Persisting an Entity Instance

エンティティXに適用されるpersist操作のセマンティクスは次のとおりです。

  • Xが新しく(new)生成されたエンティティならば管理される(managed)。エンティティXはトランザクションのコミット時もしくはコミット前にDBに追加される、またはflush操作の実行の結果としてDBに追加される。
  • Xがすでに存在する管理された(managed)エンティティならば、persist操作は無視される。しかし、Xから他のエンティティへのリレーションシップにcascade=PERSISTもしくはcascade=ALLがアノテートされているならばpersist操作がXに参照されるエンティティにカスケードされる。
  • Xが削除された(removed)エンティティならば管理される(managed)。
  • Xが切り離された(detached)オブジェクトならばIllegalArgumentExceptionがスローされる(すなわちトランザクションのコミットが失敗する)。
  • Xからのリレーションシップによって参照されるすべてのエンティティYについて、XからYへのリレーションシップがcascade=PERSISTもしくはcascade=ALLとアノテートされている場合、persistの操作はYに適用される。

全部試すのは大変そう。まず最後のやつを試してみます。AddressがエンティティXでEmployeeがエンティティY。OneToManyアノテーションcascade要素にCascadeType.ALLを指定してます。

@Entity(access=AccessType.FIELD)
public class Employee implements Serializable {

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

  private String name;

  @ManyToOne
  private Address address;
  
  // getter, setter省略

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

  private String name;
  
  @OneToMany(mappedBy="address", cascade = CascadeType.ALL )
  private Collection employees = new HashSet(); 

  public void addEmployee(Employee employee) {
    this.employees.add(employee);
    employee.setAddress(this);
  }
  
  // getter, setter省略
}

実行クラス。Queryも使ってみました。query.getResultList()で返ってくるListの値は自分でキャストしなきゃいけないんですね。ジェネリクスが使えるのかなーとおもったんですが、

@Stateless
public class ClientBean implements Client {

  @PersistenceContext
  private EntityManager em;

  public void main() {
    Address address = new Address();
    address.setName("京都");

    Employee employee1 = new Employee();
    employee1.setName("ゴン");
    address.addEmployee(employee1);

    Employee employee2 = new Employee();
    employee2.setName("うさはな");
    address.addEmployee(employee2);

    em.persist(address);

    Query query = em.createQuery("from Employee");
    for (Object each : query.getResultList()) {
      Employee e = (Employee) each;
      System.out.println(e.getName() + " : " + e.getAddress().getName());
    }

  }

  public static void main(String[] args) throws Exception {
    EJB3StandaloneBootstrap.boot(null);
    EJB3StandaloneBootstrap.deployXmlResource("ejb3-deployment.xml");
    InitialContext ctx = new InitialContext();
    Client client = (Client) ctx.lookup(Client.class.getName());
    client.main();
    EJB3StandaloneBootstrap.shutdown();
  }
}

実行結果

Hibernate: insert into Address (name, id) values (?, null)
Hibernate: call identity()
Hibernate: insert into Employee (name, address_id, id) values (?, ?, null)
Hibernate: call identity()
Hibernate: insert into Employee (name, address_id, id) values (?, ?, null)
Hibernate: call identity()
Hibernate: select employee0_.id as id1_, employee0_.name as name1_, employee0_.address_id as address3_1_ from Employee employee0_
ゴン : 京都
うさはな : 京都

うまくいっているぽっい。selectが実行されているのでデータの取得はDBからみたいです。実は、insertはトランザクションの終了直前で行われるものだとばかり思っていて、トランザクション終了直前まで問い合わせはpersistence contextの値を使うのだと思ってました。あれ、でもAddressテーブルには問い合わせしてない。Addressのデータだけpersistence context中の値が使われるんでしょうか?


「Xが削除された(removed)エンティティならば管理される(managed)」というのも試してみたのですが、これはうまくいきませんでした。例外が返ってきました。内部的にはorg.hibernate.ObjectDeletedExceptionがおきていて、「deleted entity passed to persist」というメッセージが出ています。まさにそのメッセージどおりのことをしたんですけど…。