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

今回は永続コンテキストについてです。

3.3 Persistence Context

永続コンテキストには2種類あります。単一のトランザクション完了時に終了するものと複数のトランザクションにまたがるものです。複数のトランザクションにまたがる永続コンテキストは拡張永続コンテキスト(extended persistence context)というそうです。

永続コンテキストはPersistContextアノテーションの要素にPersistenceContextType.TRANSACTIONもしくはPersistenceContextType.EXTENDEDを指定することで定義します。defaultはPersistenceContextType.TRANSACTIONです。ということでいままで使ってきたのは全部PersistenceContextType.TRANSACTIONのほうでした。

3.3.1 Extended Persistence Context

拡張永続コンテキストを使用する場合、拡張永続コンテキストはEntityManagerが生成されてcloseされるまでの間存在し続けるそうです。拡張永続コンテキストは複数のトランザクションにまたがることができて、拡張永続コンテキストがある間は非トランザクショナルな呼び出しをEntityManagerに対して行えるそうです。

EntityManagerへの非トランザクショナルな呼び出しってどういうときにするんだろう。

Java Persistence APIのドキュメントには書いてないのですが、Hibernate EntityManagerのドキュメントをみてみると、拡張永続コンテキストはステートフルセッションBeanと組み合わせて使われるとあります。でこのとき、FlushModeType.NEVERを使ってflushを明示的に行うと(パフォーマンス的に&更新の制御として?)役立つらしいです。

拡張永続コンテキストとステートレスセッションBeanの組み合わせはあり得ないようです。試してみたら例外くらいました。例外メッセージは「EXTENDED persistence contexts can only be used within a Stateful Session bean」でした。


では拡張永続コンテキストを試してみます。JBossのサンプルを適当に改変してます。
受注と受注明細のエンティティです。ManyToOne/OneToManyのリレーションシップをつかってます。

@Entity(access = AccessType.FIELD)
@Table(name="ODR")
public class Order {

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

  private double total;

  @OneToMany(cascade = CascadeType.ALL, mappedBy = "order")
  private Collection lineItems  = new ArrayList();
  
 //getter, setter省略
}
@Entity(access = AccessType.FIELD)
public class LineItem {
  
  @Id(generate = GeneratorType.AUTO)
  private int id;

  private String product;
  
  private int quantity;

  private double subtotal;

  @ManyToOne
  private Order order;

 //getter, setter省略
}

ひさびさのステートフルセッションBean。拡張永続コンテキストを使用します。addPurchaseメソッドに@FlushMode(FlushModeType.NEVER)を指定して、flushを抑制してます。flushはcheckoutメソッドで明示的に行います。ビジネスインタフェースの定義は省略。

@Stateful
public class ShoppingCartBean implements ShoppingCart {

  @PersistenceContext(type = PersistenceContextType.EXTENDED)
  private EntityManager em;

  private Order order;

  @FlushMode(FlushModeType.NEVER)
  public void addPurchase(String product, int quantity, double price) {    
    if (order == null) {
      order = new Order();
      em.persist(order);      
    }
    
    LineItem item = new LineItem();
    item.setOrder(order);
    item.setProduct(product);
    item.setQuantity(quantity);
    item.setSubtotal(quantity * price);

    order.setTotal(order.getTotal() + item.getSubtotal());
    order.getLineItems().add(item);
  }

  @Remove
  public void checkout() {
    em.flush();
  }
}

クライアント

public class Client {

  public static void main(String[] args) throws Exception {
    EJB3StandaloneBootstrap.boot(null);
    EJB3StandaloneBootstrap.deployXmlResource("ejb3-deployment.xml");
    InitialContext ctx = new InitialContext();
    
    ShoppingCart shoppingCart = (ShoppingCart) ctx.lookup(ShoppingCart.class.getName());
    System.out.println("購入1");
    shoppingCart.addPurchase("ポテチ", 2, 300);
    System.out.println("購入2");
    shoppingCart.addPurchase("コーラ", 3, 360);
    System.out.println("精算");
    shoppingCart.checkout();
    
    EJB3StandaloneBootstrap.shutdown();
  }
}

出力結果

購入1
Hibernate: insert into ODR (total, id) values (?, null)
Hibernate: call identity()
購入2
精算
Hibernate: insert into LineItem (product, quantity, subtotal, order_id, id) values (?, ?, ?, ?, null)
Hibernate: call identity()
Hibernate: insert into LineItem (product, quantity, subtotal, order_id, id) values (?, ?, ?, ?, null)
Hibernate: call identity()
Hibernate: update ODR set total=? where id=?

あれ、予想通りの結果じゃないです。ODRテーブルへのinsertも精算時に行われるのかなと思っていたのですが...。newからmanagedに状態が変更されたときは強制的にflushされてしまうのか?むー。

コードを書いて動かしてみたものの、例として適当かというと怪しくて、実はOrderエンティティのpersitをcheckoutメソッド内でやれば拡張永続コンテキストもFlushModeType.NEVERもいらなかったりします。今回のように親エンティティと子エンティティを同時に追加という例ではなくて、たとえば、親エンティティをDBから読み込んでステートフルセッションBeanで保持して、複数のトランザクションに分けて変更したり子エンティティを追加したりという例ならばよかったかもしれないです。