Extended Persistence Context の使い道

JSR-220 JPA(Java Persistence API)では、Persistence Contextという概念が定義されています。これはTransaction Contextのアナロジーで、永続オブジェクトを管理する単位となります(Persistence Contextはトランザクションとは独立であることに注意) 。

Persistence Contextを管理するのがEntity Managerであり、JPAではまさにこのEntity ManagerのAPIを定義しています。Java EE環境では、Dependency InjectionやJNDIという手段で、コンポーネント間でPersistence Context(を管理するEntity Manager)を伝播させることができます。このようにJava EEコンテナによって管理されるEntity ManagerをContainer-Managed Entity Managerと呼びます。

Persistence ContextにはTransaction-scoped Persistence ContextとExtended Persistence Contextの2種類があります。Transaction-scopedの方は文字通りトランザクション単位での管理です。一方、Extendedの方は、コンテナによってcloseされるかまたはEntityManager APIで明示的にcloseされるまで、トランザクションをまたがって管理することが可能です。

Extended Persistence ContextはWebアプリでのエンティティのキャッシュ*1として使えます。具体的には、Stateful SessionBeanとExtended Persistence Contextを組み合わせることによってリクエストをまたがったConversation状態の保持が可能になります。次の例はJPA Proposed Final Draft 5.8.2から引用したものです。

5.8.2 Container-managed Extended Persistence Context

@Stateful
@Transaction(REQUIRES_NEW)
public class ShoppingCartImpl implements ShoppingCart {
    @PersistenceContext(type=EXTENDED)
    EntityManager em;

    private Order order;
    private Product product;

    public void initOrder(Long id) {
        order = em.find(Order.class, id);
    }

    public void initProduct(String name) {
        product = (Product) em.createQuery("select p from Product p where p.name = :name")
                .setParameter("name", name)
                .getSingleResult();
    }

    public LineItem createLineItem(int quantity) {
        LineItem li = new LineItem(order, product, quantity);
        order.getLineItems().add(li);
        return li;
    }
}

参考:

*1:キャッシュと書くと誤解を招くので、Hibernate In Actionにならってアプリケーション・トランザクションと書いた方が適切かも

JBossSeamにおけるStateful SessionBean + Extended Persistence Contextの例

JBossSeam Tutorialを見ると、Conversation contextにStateful SessionBeanをセットしています(JBoss SeamのConversation contextはServlet Sessionによって実現されています)。

Let's see how the booking example application uses a conversation-scoped stateful session bean to achieve a natural cache of persistent data related to the conversation. 

...

Example 1.9. 

@Stateful                                                                               
@Name("hotelBooking")
@Interceptor(SeamInterceptor.class)
@Conversational(ifNotBegunOutcome="main")                                                
@LoggedIn                                                                                
public class HotelBookingAction implements HotelBooking, Serializable
{
   private static final Logger log = Logger.getLogger(HotelBooking.class);

   @PersistenceContext(type=EXTENDED)                                                 
   private EntityManager bookingDatabase;
   ...

実は、Stateful SessionBeanもEntityManagerもスレッド・セーフではないという仕様ですので、Servletインスタンス変数に直接Stateful SessionBeanを格納するとアクセスごとにスレッドの同期を取る必要があります。JBoss Seamは、この問題をConversation contextにStateful SessionBeanを格納することで巧妙に回避しているように思えます。

コンテナが管理するContext

メソッドの呼び出しパラメタに明示的に指定しなくとも、コンテナはcallerからcalleeへ情報を暗に伝えてくれます。その暗に伝達される情報がContextです。

  • Transaction Context: 呼び出し間でトランザクション情報を伝播させる
  • Persistence Context: 呼び出し間で永続オブジェクト情報を伝播させる
  • Security Context:呼び出し間で認証情報を伝播させる

実装上は、どれもThreadLocalに保持するのでしょうね。きっと。