EJB 3.0(Public Draft)入門記 Java Persistence API Chapter5 その6

前回は拡張永続コンテキストを使ってアプリケーショントランザクションを実験してみましたが、今回はトランザクションスコープの永続コンテキストとdetachedなエンティティの組み合わせでアプリケーショントランザクションを実験してみようと思います。


シナリオは前回と同じです。

前回と異なる実験のポイントは次のとおり。

  • エンティティは前回と同じ。
  • Defaultのトランザクションスコープの永続コンテキストを使う。
    • LogicとDAOのクラスにはステートレスセッションBeanを使う。
    • 事前データの作成や結果の取得を行うTestDaoの実装は前回と同じくステートフルセッションBeanで、なおかつ拡張永続コンテキストを使っています。
  • 前回はステートフルセッションBeanで管理していたOrderエンティティはdetachedなエンティティとしてクライアントで管理する。
  • Orderエンティティを引数で受けて戻り値で返すようにLogicやDAOを変更した。
  • 購入を行うたびにOrderエンティティをリロードしバージョンチェックを行う。


以下、今回使うコードと実行結果です。

StatelessOrderLogic:Logicのインタフェース

public interface StatelessOrderLogic {

  Order startOrder(String orderCode, Customer customer);
  
  Order buy(Order order, Product product, int quantity);
  
  Order checkOut(Order order);
  
}

StatelessOrderLogicImpl:Logicの実装

@Stateless
public class StatelessOrderLogicImpl implements StatelessOrderLogic {

  @EJB
  private StatelessOrderDao dao;

  private Order order;

  public Order startOrder(String orderCode, Customer customer) {
    order = new Order(orderCode, customer);
    dao.create(order);
    return order;
  }

  public Order buy(Order order, Product product, int quantity) {
    dao.checkVersion(order);
    LineItem item = new LineItem(order, product, quantity);
    order.getLineItems().add(item);
    return order;
  }

  public Order checkOut(Order order) {
    dao.merge(order);
    return order;
  }
}

StatelessOrderDao:DAOのインタフェース

public interface StatelessOrderDao {
  
  void create(Order order);
  
  void checkVersion(Order order);
  
  Order merge(Order order);

}

StatelessOrderDaoImpl:DAOの実装。永続コンテキストのタイプはDefaultのトランザクションスコープです。バージョンチェックのロジック持ってます。

@Stateless
public class StatelessOrderDaoImpl implements StatelessOrderDao {

  @PersistenceContext
  private EntityManager entityManager;

  public void setEntityManager(EntityManager entityManager) {
    this.entityManager = entityManager;
  }

  public void create(Order order) {
    entityManager.persist(order);
  }

  public void checkVersion(Order newOrder) {
    Order currentOrder = entityManager.find(Order.class, newOrder.getId());
    if (newOrder.getVersion() != currentOrder.getVersion()) {
      throw new StaleObjectStateException();
    }
  }

  public Order merge(Order order) {
    return entityManager.merge(order);
  }

}

TransactionScopePersistenceClient:クライアントです。

public class TransactionScopePersistenceClient {

  public static void main(String[] args) throws Exception {
    EJB3StandaloneBootstrap.boot(null);
    EJB3StandaloneBootstrap.scanClasspath();

    TransactionScopePersistenceClient client = new TransactionScopePersistenceClient();
    client.order();

    EJB3StandaloneBootstrap.shutdown();
  }

  public void order() throws Exception {
    InitialContext ctx = new InitialContext();

    System.out.println("# リソースデータ準備");
    TestDao testDao = (TestDao) ctx.lookup(TestDao.class.getName());
    Customer customer = new Customer("うさはな");
    testDao.create(customer);
    Product product1 = new Product("ポテチ", 150);
    testDao.create(product1);
    Product product2 = new Product("コーラ", 100);
    testDao.create(product2);

    System.out.println("\n# 注文開始");
    StatelessOrderLogic logic = (StatelessOrderLogic) ctx.lookup(StatelessOrderLogic.class.getName());
    Order order = logic.startOrder("ORDER-001", customer);

    System.out.println("\n# 購入");
    order = logic.buy(order, product1, 10);
    order = logic.buy(order, product2, 20);

    // System.out.println("\n# 別のセッションBeanでOrderデータを変更");
    // Order chengedTarget = dao.findOrderById(uncheckeOutOrder.getId());
    // dao.chengeOrderCode(chengedTarget, "CHENGED-" +
    // chengedTarget.getOrderCode());

    System.out.println("\n# 確定");
    logic.checkOut(order);

    System.out.println("\n# 確認:データベース内のOrderの表示");
    print(testDao.getAllOrders());
  }

  public void print(Order order) {
    for (LineItem item : order.getLineItems()) {
      System.out.println(
          order.getOrderCode() + "-" + item.getOrderSeq() + ", " 
          + order.getCustomer().getName() + ", " 
          + item.getProduct().getName() + ", " 
          + item.getQuantity());
    }
  }

  public void print(List orders) {
    for (Order order : orders) {
      print(order);
    }
  }
}

実行結果

# リソースデータ準備
Hibernate: insert into Customer (version, name, id) values (?, ?, null)
Hibernate: call identity()
Hibernate: insert into Product (name, price, version, id) values (?, ?, ?, null)
Hibernate: call identity()
Hibernate: insert into Product (name, price, version, id) values (?, ?, ?, null)
Hibernate: call identity()

# 注文開始
Hibernate: insert into ORDERDATA (orderCode, customer_id, version, id) values (?, ?, ?, null)
Hibernate: call identity()

# 購入
Hibernate: select order0_.id as id2_1_, order0_.orderCode as orderCode2_1_, order0_.customer_id as customer4_2_1_, order0_.version as version2_1_, customer1_.id as id0_0_, customer1_.version as version0_0_, customer1_.name as name0_0_ from ORDERDATA order0_ left outer join Customer customer1_ on order0_.customer_id=customer1_.id where order0_.id=?
Hibernate: select order0_.id as id2_1_, order0_.orderCode as orderCode2_1_, order0_.customer_id as customer4_2_1_, order0_.version as version2_1_, customer1_.id as id0_0_, customer1_.version as version0_0_, customer1_.name as name0_0_ from ORDERDATA order0_ left outer join Customer customer1_ on order0_.customer_id=customer1_.id where order0_.id=?

# 確定
Hibernate: select order0_.id as id2_1_, order0_.orderCode as orderCode2_1_, order0_.customer_id as customer4_2_1_, order0_.version as version2_1_, lineitems1_.order_id as order5_3_, lineitems1_.id as id3_, lineitems1_.id as id1_0_, lineitems1_.orderSeq as orderSeq1_0_, lineitems1_.quantity as quantity1_0_, lineitems1_.order_id as order5_1_0_, lineitems1_.product_id as product6_1_0_, lineitems1_.version as version1_0_ from ORDERDATA order0_ left outer join LineItem lineitems1_ on order0_.id=lineitems1_.order_id where order0_.id=?
Hibernate: select customer0_.id as id0_0_, customer0_.version as version0_0_, customer0_.name as name0_0_ from Customer customer0_ where customer0_.id=?
Hibernate: select product0_.id as id3_0_, product0_.name as name3_0_, product0_.price as price3_0_, product0_.version as version3_0_ from Product product0_ where product0_.id=?
Hibernate: insert into LineItem (orderSeq, quantity, order_id, product_id, version, id) values (?, ?, ?, ?, ?, null)
Hibernate: call identity()
Hibernate: select product0_.id as id3_0_, product0_.name as name3_0_, product0_.price as price3_0_, product0_.version as version3_0_ from Product product0_ where product0_.id=?
Hibernate: insert into LineItem (orderSeq, quantity, order_id, product_id, version, id) values (?, ?, ?, ?, ?, null)
Hibernate: call identity()
Hibernate: update ORDERDATA set orderCode=?, customer_id=?, version=? where id=? and version=?

# 確認:データベース内のOrderの表示
Hibernate: select order0_.id as id2_, order0_.orderCode as orderCode2_, order0_.customer_id as customer4_2_, order0_.version as version2_ from ORDERDATA order0_
Hibernate: select lineitems0_.order_id as order5_2_, lineitems0_.id as id2_, lineitems0_.id as id1_1_, lineitems0_.orderSeq as orderSeq1_1_, lineitems0_.quantity as quantity1_1_, lineitems0_.order_id as order5_1_1_, lineitems0_.product_id as product6_1_1_, lineitems0_.version as version1_1_, product1_.id as id3_0_, product1_.name as name3_0_, product1_.price as price3_0_, product1_.version as version3_0_ from LineItem lineitems0_ left outer join Product product1_ on lineitems0_.product_id=product1_.id where lineitems0_.order_id=?
ORDER-001-0, うさはな, ポテチ, 10
ORDER-001-1, うさはな, コーラ, 20


感想など

  • 今回のサンプルではdetachedなエンティティはOrderのみでコードは複雑にはならない。引数や戻り値でOrderエンティティをやり取りするだけ。関連がない複数のdetachedなエンティティが同じアプリケーショントランザクションに含まれていて、それぞれをクライアント側(やWeb層)で管理する必要がある場合はすこし面倒くさい。その場合は複数のdetachedなエンティティを束ねるDTOを用意するのかなぁ。
  • 前回とまったく同じロジックにするならば明示的なバージョンチェックはいらない(mergeのときに自動でチェックしてくれる)のですが、入れてみました。
  • やっぱりcheckOutメソッドを呼んだ時にORDERDATAテーブルへのupdateが行われている。そんなつもりないのになんでだろ?関連のCollectionにaddしているからとか?


Chapter5はこれで終わりの予定です。