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(Listorders) { 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はこれで終わりの予定です。