トランザクション管理
多くの企業向けアプリケーションで非常に重要なもう1つの要素が、トランザクション管理です。大規模なアプリケーションでは、多数のユーザーが同一のデータに同時にアクセスを試みる可能性があるためです。また、単独ユーザーでも複数ユーザーでも、通常は特定の操作を完全に成功させるか失敗させるかのどちらかにする必要があります。
トランザクション管理 - 機能の比較
トランザクション性を備えたSpringおよびEJB 3.0の機能的性能を説明するために、航空券予約シナリオにおけるチケット購入の事例を考えてみましょう(図2を参照)。
以下に示す単体テストは、このチケット購入の処理がトランザクション的な方法で機能することを検証するものです。
public void testPurchaseTicket_DebitFailure() throws Exception { // Creates a pending ticket Ticket ticket = createTicket(); // Store original ticket count from DB int count = ticketDAO.findAll().size(); // Setup a credit authorizer which will throw an exception on debit setupMockAuthorizer(true); try { bookingAgent.purchaseTicket(ticket, true); fail("System failure was not thrown as was intended"); } catch (InsufficientFundsException e) { // Correct behavior since we're testing transactional semantics } // Verify transaction rolled-back assertEquals(count, ticketDAO.findAll().size()); }
このテストは、SpringとEJB 3.0のどちらの実装に対しても正しく実行することができます。Springによる実装は次のようになります。
public Ticket purchaseTicket(Ticket ticket) { creditAuthorizer.authorize(ticket.getPrice(), null); ticket.setStatus(Status.PURCHASED); ticketDAO.save(ticket); creditAuthorizer.debit(ticket.getPrice()); return ticket; }
これを以下のEJB 3.0仕様による実装と比較してみましょう。
public Ticket purchaseTicket(Ticket ticket) { creditAuthorizer.authorize(ticket.getPrice(), null); ticket.setStatus(Status.PURCHASED); ticketDAO.save(ticket); creditAuthorizer.debit(ticket.getPrice()); return ticket; }
両実装がまったく同じものであり、どちらもトランザクション管理を明示的に処理する必要がないことに注意してください。このことは宣言的プログラミングの真価をよく示しています。開発者は業務上の問題に重点を置いたコードを書くことができ、トランザクション性のような各分野共通の問題はモジュール化してアプリケーションの種類に関係なく適用することが可能なのです。この事例では、SpringもEJB 3.0もトランザクションの処理にあたって同等の機能を提供しており、どちらもビジネスロジックの煩雑化を防ぐやり方でこの機能のモジュール化に成功しています。
トランザクション管理 - 機能以外の比較
検討すべきトランザクション管理の重要な側面として、境界設定(demarcation)、伝播(propagation)、分離(isolation)があります。境界設定とは、トランザクション的な意味で作業単位の境界を決める処理をいいます。また、複数のコンポーネントが協調する場合、コンポーネント間でトランザクションの共有(伝播)が必要になります。さらに、開発者によるトランザクション間の隔たりの度合いの調整を可能にしているのが分離レベルです。
Springでは、トランザクションの境界設定、伝播、分離はすべてAOP(Aspect Oriented Programming)の機能を介して宣言されています。トランザクション的プロキシはそのXML設定フィルタ内で明示的に定義するか、アスペクト構文を使って適用することができます。トランザクション的プロキシにおけるワイヤリング方法を以下に示します。
<bean id="bookingAgent" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean"> <property name="transactionManager" ref="transactionManager" /> <property name="target" ref="bookingAgentSpring" /> <property name="transactionAttributes"> <props> <prop key="*">PROPAGATION_REQUIRED</prop> </props> </property> </bean> <bean id="bookingAgentSpring" class="org.jug.flight.booking.spring.BookingAgentSpring"> <property name="flightDAO" ref="flightDAO" /> <property name="ticketDAO" ref="ticketDAO" /> <property name="screener" ref="screener" /> </bean>
一方のEJB 3.0は、特別な設定が必要にならないように、セッションBeanのすべてのpublic
メソッドに対してトランザクション的な意味を自動的に適用します。次に示すように、開発者は注釈またはXMLを用いてトランザクションの伝播レベルを調整できます。
@TransactionAttribute(TransactionAttributeType.REQUIRED)
public Ticket purchaseTicket(Ticket ticket)
{
...
}
EJB 3.0仕様はトランザクションの分離をリソースマネージャ特有の形で宣言するため、EJB 3.0にはこの要求を表現する標準的な方法がありません。SpringにはJDBC、Hibernate、JTAなど、さまざまな種類のトランザクションAPIが統合されていますが、EJB 3.0はJTAのトランザクションしかサポートしていません。トランザクションAPIの選択は、2つの初歩的な場合で重要になります。まず、すべてのEJBコンテナはJTAをサポートしていますが、すべてのコンテナがJTAをサポートしているわけではありません(例えばTomcatなど)。さらに、複数のリソース(JDBC、JMSなど)を1つの分散トランザクション内で連結するには、JTAによる実装が必要です。なお、JTAが必要なのにJTA実装を提供していない環境で作業を行う場合には、Java Open Transaction Manager(JOTM)のようなオープンソースのJTA実装を検討するとよいでしょう。
トランザクション管理 - まとめ
SpringとEJB 3.0はどちらも、その持続性のメカニズムと互換性のあるトランザクション管理に対して統合化アプローチをとっています(表3を参照)。
機能 | Spring | EJB 3.0 |
宣言的トランザクション | √ | √ |
プログラム的トランザクション | √ | √ |
境界設定 | AOP | セッションBeanのメソッド |
サポートするトランザクションの種類 | JDBC、Hibernate、JTA | JTA |
分散トランザクションのサポート | √(JTAを使用) | √ |
設定 | XML | デフォルトはトランザクション的なもの、注釈またはXMLによるオーバーライドあり |
標準 | √(JTA) | √ |