Declarative Transaction Management




Declarative Transaction Management

One of the primary advantages of Enterprise JavaBeans is that it allows for declarative transaction management . Without this feature, transactions must be controlled using explicit transaction demarcation, which involves the use of fairly complex APIs like the OMG's Object Transaction Service (OTS) or its Java implementation, the Java Transaction Service (JTS). At best, explicit demarcation is difficult if you use the aforementioned APIs, particularly if you are new to transactional systems. In addition, it requires that the transactional code be written within the business logic, which reduces the clarity of the code. We talk more about explicit transaction management and EJB later in this chapter.

With declarative transaction management, the transactional behavior of EJBs can be controlled using the @javax.ejb.TransactionAttribute annotation or the EJB deployment descriptor, both of which can set transaction attributes for individual enterprise bean methods. This means that the transactional behavior of an EJB can be changed without changing the EJB's business logic by simply annotating the method in a different way or modifying XML. Declarative transaction management reduces the complexity of transactions for EJB developers and application developers and makes it easier to create robust transactional applications.

Transaction Scope

Transaction scope is a crucial concept for understanding transactions. In this context, transaction scope refers to those EJBsboth session and entitythat are participating in a particular transaction. In the bookPassage( ) method of the TravelAgent EJB, all the EJBs involved are part of the same transaction scope. The scope of the transaction starts when a client invokes the TravelAgent EJB's bookPassage( ) method. Once the transaction scope has started, it is propagated to both the entity manager service that is responsible for creating reservations and the ProcessPayment EJB.

As you know, a transaction is a unit-of-work made up of one or more tasks. In a transaction, all the tasks that make up the unit-of-work must succeed for the entire transaction to succeed; in other words, the transaction must be atomic. If any task fails, the updates made by all the other tasks in the transaction will be rolled back or undone. In EJB, tasks are expressed as enterprise bean methods, and a unit-of-work consists of every enterprise bean method invoked in a transaction. The scope of a transaction includes every EJB that participates in the unit-of-work.

It is easy to trace the scope of a transaction by following the thread of execution. If the invocation of the bookPassage( ) method begins a transaction, then logically, the transaction ends when the method completes. The scope of the bookPassage( ) TRansaction would include the TravelAgent EJB, the EntityManager service, and the ProcessPayment EJBevery EJB or transactional-aware service touched by the bookPassage( ) method. A transaction is propagated to an EJB when that EJB's method is invoked and included in the scope of that transaction. The transaction is also propagated to the persistence context of an EntityManager. The persistence context keeps track of changes made to persistent managed objects and commits them if the transaction succeeds.

A transaction can end if an exception is thrown while the bookPassage( ) method is executing. The exception can be thrown from one of the other EJBs or from the bookPassage( ) method itself. An exception may or may not cause a rollback, depending on its type. We'll discuss exceptions and transactions in more detail later.

The thread of execution is not the only factor that determines whether an EJB is included in the scope of a transaction; the EJB's transaction attributes also play a role. Determining whether an EJB participates in the transaction scope of any unit-of-work is accomplished implicitly, using the EJB's transaction attributes, or explicitly, using the JTA.

Transaction Attributes

As an application developer, you don't normally need to control transactions explicitly when using an EJB server. EJB servers can manage transactions implicitly, based on the transaction attributes established at deployment time. When an EJB is deployed, you can set its runtime transaction attribute in the @javax.ejb.TransactionAttribute annotation or deployment descriptor to one of several values:

NotSupported
Supports
Required
RequiresNew
Mandatory
Never

You can set a transaction attribute for the entire EJB (in which case it applies to all methods) or you can set different transaction attributes for individual methods. The former method is much simpler and less error-prone, but setting attributes at the method level offers more flexibility. The code in the following sections shows how to set the default transaction attribute of an EJB in the EJB's deployment descriptor.

Using the @TransactionAttribute annotation

The @javax.ejb.TransactionAttribute annotation can be used to apply transaction attributes to your EJB's bean class. The attribute is defined using the javax.ejb.TransactionAttributeType Java enum:

public enum TransactionAttributeType {
   MANDATORY,
   REQUIRED,
   REQUIRES_NEW,
   SUPPORTS,
   NOT_SUPPORTED,
   NEVER
}

@Target({METHOD, TYPE})
public @interface TransactionAttribute {
   TransactionAttributeType value( ) default TransactionAttributeType.REQUIRED;
}

The @transactionAttribute can be applied per method, or you can use it on the bean class to define the default transaction attribute for the entire bean class:

import static TransactionAttributeType.*;

@Stateless
@TransactionAttribute(NOT_SUPPORTED)
public class TravelAgentBean implements TravelAgentRemote {

   public void setCustomer(Customer cust) {...}

   @TransactionAttribute(REQUIRED) 

   public TicketDO bookPassage(CreditCardDO card, double price) {
      ...
   }
}

In this example, the default transaction attribute will be NOT_SUPPORTED for every method of the class because we have applied the @transactionAttribute annotation to the bean class. This default can be overridden by applying @TRansactionAttribute individually to the bookPassage( ) method so that it has a REQUIRED transaction attribute.

If you do not specify any @transactionAttribute and there is no XML deployment descriptor, the default transaction attribute will be REQUIRED. One of the ideas behind EJB 3.0 is to provide common defaults so that you do not have to be explicit about transaction demarcation. In the majority of cases, EJB methods will be transactional, especially if they are interacting with an entity manager.


Setting a transaction attribute within XML

In the XML deployment descriptor, a <container-transaction> element specifies the transaction attributes for the EJBs described in the deployment descriptor:

<ejb-jar xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
                             http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
         version=3.0>
    <assembly-descriptor>
        <container-transaction>
            <method>
                <ejb-name>TravelAgentEJB</ejb-name>
                <method-name> * </method-name>
            </method>
            <trans-attribute>NotSupported</trans-attribute>
        </container-transaction>
        <container-transaction>
            <method>
                <ejb-name>TravelAgentEJB</ejb-name>
                <method-name>bookPassage</method-name>
            </method>
            <trans-attribute>Required</trans-attribute>
        </container-transaction>
    </assembly-descriptor>
</ejb-jar>

This deployment descriptor specifies the transaction attributes for the TravelAgent EJB. Each <container-transaction> element specifies a method and that method's transaction attribute. The first <container-transaction> element specifies that all methods have a transaction attribute of NotSupported by default; the * is a wildcard that indicates all the methods of the TravelAgent EJB. The second <container-transaction> element overrides the default setting to specify that the bookPassage( ) method has a Required transaction attribute. Note that we have to specify which EJB we are referring to with the <ejb-name> element; an XML deployment descriptor can cover many EJBs.

Transaction attributes defined

Here are the definitions of the transaction attributes listed earlier. In a few of the definitions, the client transaction is described as suspended . This means the transaction is not propagated to the enterprise bean method being invoked; propagation of the transaction is temporarily halted until the enterprise bean method returns. To make things easier, we will talk about attribute types as if they were bean types: for example, we'll say "a Required EJB" as shorthand for "an enterprise bean with the Required transaction attribute." The attributes are:


NotSupported

Invoking a method on an EJB with this transaction attribute suspends the transaction until the method is completed. This means that the transaction scope is not propagated to the NotSupported EJB or to any of the EJBs it calls. Once the method on the NotSupported EJB is done, the original transaction resumes its execution.

Figureshows that a NotSupported EJB does not propagate the client transaction when one of its methods is invoked.

The NotSupported attribute


Supports

This attribute means that the enterprise bean method will be included in the transaction scope if it is invoked within a transaction. In other words, if the EJB or client that invokes the Supports EJB is part of a transaction scope, the Supports EJB and all EJBs accessed by it become part of the original transaction. However, the Supports EJB doesn't have to be part of a transaction and can interact with clients and other EJBs that are not included in a transaction scope.

Figurea shows the Supports EJB being invoked by a transactional client and propagating the transaction. Figureb shows the Supports EJB being invoked by a nontransactional client.

The Supports attribute


Required

This attribute means that the enterprise bean method must be invoked within the scope of a transaction. If the calling client or EJB is part of a transaction, the Required EJB is automatically included in its transaction scope. If, however, the calling client or EJB is not involved in a transaction, the Required EJB starts its own new transaction. The new transaction's scope covers only the Required EJB and all other EJBs accessed by it. Once the method invoked on the Required EJB is done, the new transaction's scope ends.

Figurea shows the Required EJB being invoked by a transactional client and propagating the transaction. Figureb shows the Required EJB being invoked by a nontransactional client, which causes it to start its own transaction.

The Required attribute


RequiresNew

This attribute means that a new transaction is always started. Regardless of whether the calling client or EJB is part of a transaction, a method with the RequiresNew attribute begins a new transaction when invoked. If the calling client is already involved in a transaction, that transaction is suspended until the RequiresNew EJB's method call returns. The new transaction's scope covers only the RequiresNew EJB and all the EJBs accessed by it. Once the method invoked on the RequiresNew EJB is done, the new transaction's scope ends and the original transaction resumes.

Figurea shows the RequiresNew EJB being invoked by a transactional client. The client's transaction is suspended while the EJB executes under its own transaction. Figureb shows the RequiresNew EJB being invoked by a nontransactional client; the RequiresNew EJB executes under its own transaction.

The RequiresNew attribute


Mandatory

This attribute means that the enterprise bean method must always be made part of the transaction scope of the calling client. The EJB may not start its own transaction; the transaction must be propagated from the client. If the calling client is not part of a transaction, the invocation will fail, throwing a javax.ejb.EJBTransactionRequiredException .

Figurea shows the Mandatory EJB invoked by a transactional client and propagating the transaction. Figureb shows the Mandatory EJB invoked by a nontransactional client; the method throws an EJBTransactionRequiredException because there is no transaction scope.

The Mandatory attribute


Never

This attribute means that the enterprise bean method must not be invoked within the scope of a transaction. If the calling client or EJB is part of a transaction, the Never EJB will throw an EJBException. However, if the calling client or EJB is not involved in a transaction, the Never EJB will execute normally without a transaction.

Figurea shows the Never EJB being invoked by a nontransactional client. Figureb shows the Never EJB being invoked by a transactional client; the method throws an EJBException to EJB clients because a client or EJB that is included in a transaction can never invoke the method.

The Never attribute


EJB 3.0 persistence and transaction attributes

The EJB specification strongly advises that EntityManagers be accessed within the scope of a JTA transaction. So, if you are wrapping access to your persistent entities with EJBs, use only the Required, RequiresNew, and Mandatory transaction attributes. This restriction ensures that all database access occurs in the context of a transaction, which is important when the container is automatically managing persistence. There are valid exceptions to this rule when using extended persistence contexts with stateful session beans, but we'll talk about these exceptions later in the chapter.

Message-driven beans and transaction attributes

Message-driven beans may declare only the NotSupported or Required transaction attribute. The other transaction attributes don't make sense in message-driven beans because they apply to client-initiated transactions. The Supports, RequiresNew, Mandatory, and Never attributes are all relative to the transaction context of the client. For example, the Mandatory attribute requires the client to have a transaction in progress before calling the enterprise bean. This is meaningless for a message-driven bean, which is decoupled from the client.

The NotSupported transaction attribute indicates that the message will be processed without a transaction. The Required transaction attribute indicates that the message will be processed with a container-initiated transaction.

EJB endpoints and transaction attributes

The Mandatory TRansaction attribute cannot be used with EJB endpoints because an EJB endpoint does not propagate a client transaction. This may change when web service transactions become standardized, but for now, using Mandatory with an EJB endpoint method is prohibited.

Transaction Propagation

To illustrate the impact of transaction attributes, we'll look once again at the bookPassage( ) method of the TravelAgent EJB. In order for bookPassage( ) to execute as a successful transaction, both the creation of the Reservation entity and the charge to the customer must be successful. This means both operations must be included in the same transaction. If either operation fails, the entire transaction fails. We could have specified the Required transaction attribute as the default for all the EJBs involved because that attribute enforces our desired policy that all EJBs must execute within a transaction and thus ensures data consistency.

As a transaction monitor, an EJB server watches each method call in the transaction. If any of the updates fail, all the updates to all the EJBs and entities will be reversed or rolled back. A rollback is like an undo command. If you have worked with relational databases, the concept of a rollback should be familiar to you. Once an update is executed, you can either commit the update or roll it back. A commit makes the changes requested by the update permanent; a rollback aborts the update and leaves the database in its original state. Making EJBs transactional provides the same kind of rollback/commit control. For example, if the Reservation entity cannot be created by the EntityManager, the charge made by the ProcessPayment EJB is rolled back. Transactions make updates an all-or-nothing proposition. This ensures that the unit-of-work, like the bookPassage( ) method, executes as intended, and it prevents inconsistent data from being written to databases.

In cases in which the container implicitly manages the transaction, the commit and rollback decisions are handled automatically. When transactions are managed explicitly within an enterprise bean or by the client, the responsibility falls on the enterprise bean or application developer to commit or roll back a transaction. Programmatic demarcation of transactions is covered in detail later in this chapter.

Let's assume that the TravelAgent EJB is created and used on a client as follows:

TravelAgent agent = (TravelAgent)jndi.lookoup("TravelAgent");
agent.setCabinID(cabin_id);
agent.setCruiseID(cruise_id);
try {
    agent.bookPassage(card,price);
} catch(Exception e) {
    System.out.println("Transaction failed!");
}

Furthermore, let's assume that the bookPassage( ) method has been given the transaction attribute RequiresNew. In this case, the client that invokes the bookPassage( ) method is not itself part of a transaction. When bookPassage( ) is invoked on the TravelAgent EJB, a new transaction is created, as dictated by the RequiresNew attribute. This means the TravelAgent EJB registers itself with the EJB server's transaction manager, which will manage the transaction automatically. The transaction manager coordinates transactions, propagating the transaction scope from one EJB to the next to ensure that all EJBs touched by a transaction are included in the transaction's unit-of-work. That way, the transaction manager can monitor the updates made by each enterprise bean and decide, based on the success of those updates, whether to commit all changes made by all enterprise beans to the database, or roll them all back. If a system exception or a rollback application exception is thrown by the bookPassage( ) method, the transaction is automatically rolled back. We talk more about exceptions later in this chapter.

When the byCredit( ) method is invoked within the bookPassage( ) method, the ProcessPayment EJB registers with the transaction manager under the transactional context that was created for the TravelAgent EJB; the transactional context is propagated to the ProcessPayment EJB. When the new Reservation entity is persisted by the entity manager, the entity manager's persistent context is also registered with the transaction manager under the same transaction. When all the EJBs and persistence contexts are registered and their updates are made, the transaction manager checks to ensure that their updates will work. If all the updates will work, the transaction manager allows the changes to become permanent. If one of the EJBs or entity managers reports an error or fails, any changes made in either the ProcessPayment or TravelAgent EJB are rolled back by the transaction manager. Figure illustrates the propagation and management of the TravelAgent EJB's transactional context.

Managing the TravelAgent EJB's transactional context


In addition to managing transactions in its own environment, an EJB server can coordinate with other transactional systems. If, for example, the ProcessPayment EJB actually came from a different application server than the TravelAgent EJB, the two application servers would cooperate to manage the transaction as one unit-of-work. This is called a distributed transaction .[*] A distributed transaction requires what is called a two-phase commit (2-PC or TPC). A 2-PC allows transactions to be managed across different servers and resources (e.g., databases and JMS providers). The details of a 2-PC are beyond the scope of this book, but a system that supports it will not require any extra operations by an EJB or application developer. If distributed transactions are supported, the protocol for propagating transactions, as discussed earlier, will be supported. In other words, as an application or EJB developer, you should not notice a difference between local and distributed transactions.

[*] Not all EJB servers support distributed transactions.

A number of books on transaction processing and 2-PC are available. Perhaps the best books on the subject are Principles of Transaction Processing (Morgan Kaufmann) and Transaction Processing: Concepts and Techniques (Morgan Kaufmann). A much lighter resource is the series of "XA Exposed" articles (I, II, and III) by Mike Spille, which you can find at http://jroller.com/page/pyrasun/?anchor=xa_exposed.


Transactions and persistence context propagation

There are some transaction propagation rules to consider when invoking on multiple different EJBs within the same transaction that use entity managers. For instance, if our ProcessPayment EJB was reimplemented to use Java Persistence rather than JDBC to log payments, its entity manager would share the same persistence context as the TRavelAgentBean's bookPassage( ) method. This is because the bookPassage( ) method invokes the ProcessPaymentBean within the same transaction. Here is a more detailed list of persistence context-propagation rules:

  • When a transaction-scoped entity manager is invoked outside the scope of a transaction, it creates a persistence context for the duration of that method call. After the method call completes, any managed objects produced by the call are immediately detached. Chapter 5 gives a detailed list of methods that are allowed to be invoked outside of a transaction.

  • If a transaction-scoped entity manager is invoked from within a transaction, a new persistence context is created if there isn't one already and associated with that transaction.

  • If an entity manager is invoked upon and a persistence context is already associated with the transaction, use that persistence context. The persistence context is propagated between EJB invocations in the same transaction. This means that if an EJB interacts with an injected entity manager within a transaction and then invokes on another EJB within that same transaction, that EJB call will use the same enlisted persistence context.

  • If an EJB with a transaction-scoped persistence context invokes on a stateful session bean that uses an extended persistence context, an error is thrown.

  • If a stateful session bean with an extended persistence context calls another EJB that has injected a transaction-scoped persistence context, the extended persistence context is propagated.

  • If an EJB calls another EJB with a different transaction scope, the persistence context, whether it is extended or not, is not propagated.

  • If a stateful session bean with an extended persistence context calls another noninjected stateful session bean with an extended persistence context, an error is thrown. We saw in Chapter 11 that if you inject a stateful session bean into another stateful session bean, those beans share the same extended persistence context. However, if you manually create a stateful session, there is no sharing of persistence contexts.