Transactions

 


Overview

To emulate a business transaction, a program may need to perform several steps. A financial program, for example, might transfer funds from a checking account to a savings account with the steps listed in the following pseudocode:

begin transaction 
   debit checking account 
   credit savings account
   update history log 
commit transaction

Either all three of these steps must complete, or none of them at all. Otherwise, data integrity is lost. Because the steps within a transaction are a unified whole, a transaction is often defined as an indivisible unit of work.

A transaction can end in two ways: with a commit or a rollback. When a transaction commits, the data modifications made by its statements are saved. If a statement within a transaction fails, the transaction rolls back, undoing the effects of all statements in the transaction. In the pseudocode, for example, if a disk drive crashed during the credit step, the transaction would roll back and undo the data modifications made by the debit statement. Although the transaction failed, data integrity would be intact because the accounts still balance.

In the preceding pseudocode, the begin and commit statements mark the boundaries of the transaction. When designing an enterprise bean, you determine how the boundaries are set by specifying either container-managed or bean-managed transactions.

 

Container-Managed Transactions

In an enterprise bean with container-managed transactions, the EJB container sets the boundaries of the transactions. You can use container-managed transactions with any type of enterprise bean: session, entity, or message-driven. Container-managed transactions simplify development because the enterprise bean code does not explicitly mark the transaction's boundaries. The code does not include statements that begin and end the transaction.

Typically, the container begins a transaction immediately before an enterprise bean method starts. It commits the transaction just before the method exits. Each method can be associated with a single transaction. Nested or multiple transactions are not allowed within a method.

Container-managed transactions do not require all methods to be associated with transactions. When deploying a bean, you specify which of the bean's methods are associated with transactions by setting the transaction attributes.

 

Transaction Attributes

A transaction attribute controls the scope of a transaction.

A transaction attribute may have one of the following values:

  • Required
  • RequiresNew
  • Mandatory
  • NotSupported
  • Supports
  • Never

Required

If the client is running within a transaction and invokes the enterprise bean's method, the method executes within the client's transaction. If the client is not associated with a transaction, the container starts a new transaction before running the method.

The Required attribute will work for most transactions. Therefore, you may want to use it as a default, at least in the early phases of development. Because transaction attributes are declarative, you can easily change them at a later time.

RequiresNew

If the client is running within a transaction and invokes the enterprise bean's method, the container takes the following steps:

  1. Suspends the client's transaction
  2. Starts a new transaction
  3. Delegates the call to the method
  4. Resumes the client's transaction after the method completes

If the client is not associated with a transaction, the container starts a new transaction before running the method.

You should use the RequiresNew attribute when you want to ensure that the method always runs within a new transaction.

Mandatory

If the client is running within a transaction and invokes the enterprise bean's method, the method executes within the client's transaction. If the client is not associated with a transaction, the container throws the TransactionRequiredException.

Use the Mandatory attribute if the enterprise bean's method must use the transaction of the client.

NotSupported

If the client is running within a transaction and invokes the enterprise bean's method, the container suspends the client's transaction before invoking the method. After the method has completed, the container resumes the client's transaction.

If the client is not associated with a transaction, the container does not start a new transaction before running the method.

Use the NotSupported attribute for methods that don't need transactions. Because transactions involve overhead, this attribute may improve performance.

Supports

If the client is running within a transaction and invokes the enterprise bean's method, the method executes within the client's transaction. If the client is not associated with a transaction, the container does not start a new transaction before running the method.

Because the transactional behavior of the method may vary, you should use the Supports attribute with caution.

Never

If the client is running within a transaction and invokes the enterprise bean's method, the container throws a RemoteException. If the client is not associated with a transaction, the container does not start a new transaction before running the method.

Summary of Transaction Attributes

Table 14-1 summarizes the effects of the transaction attributes. Both the T1 and T2 transactions are controlled by the container. A T1 transaction is associated with the client that calls a method in the enterprise bean. In most cases, the client is another enterprise bean. A T2 transaction is started by the container just before the method executes.

In the last column of Table 14-1, the word "None" means that the business method does not execute within a transaction controlled by the container. However, the database calls in such a business method might be controlled by the transaction manager of the DBMS.

Setting Transaction Attributes

Because transaction attributes are stored in the deployment descriptor, they can be changed during several phases of J2EE app development: enterprise bean creation, app assembly, and deployment. However, it is the responsibility of an enterprise bean developer to specify the attributes when creating the bean. The attributes should be modified only by an app developer who is assembling components into larger apps. Do not expect the person deploying the J2EE app to specify the transaction attributes.

 

Transaction Attribute Client's Transaction Business Method's Transaction
Required None T2
T1 T1
RequiresNew None T2
T1 T2
Mandatory None error
T1 T1
NotSupported None None
T1 None
Supports None None
T1 T1
Never None None
T1 Error

You can specify the transaction attributes for the entire enterprise bean or for individual methods. If you've specified one attribute for a method and another for the bean, the attribute for the method takes precedence. When specifying attributes for individual methods, the requirements differ with the type of bean. Session beans need the attributes defined for business methods, but do not allow them for the create methods. Entity beans require transaction attributes for the business, create, remove, and finder methods. Message-driven beans require transaction attributes (either Required or NotSupported) for the onMessage method.

 

Rolling Back a Container-Managed Transaction

There are two ways to roll back a container-managed transaction. First, if a system exception is thrown, the container will automatically roll back the transaction. Second, by invoking the setRollbackOnly method of the EJBContext interface, the bean method instructs the container to roll back the transaction. If the bean throws an app exception, the rollback is not automatic, but may be initiated by a call to setRollbackOnly. For a description of system and app exceptions, see Handling Exceptions.

The source code for the following example is in the j2eetutorial/examples/src/ejb/bank directory. To compile the code, go to the j2eetutorial/examples directory and type ant bank. To create the database tables, type ant create-bank-table. A sample BankApp.ear file is in the j2eetutorial/examples/ears directory.

The transferToSaving method of the BankEJB example illustrates the setRollbackOnly method. If a negative checking balance occurs, transferToSaving invokes setRollBackOnly and throws an app exception (InsufficientBalanceException). The updateChecking and updateSaving methods update database tables. If the updates fail, these methods throw a SQLException and the transferToSaving method throws an EJBException. Because the EJBException is a system exception, it causes the container to automatically roll back the transaction. Here is the code for the transferToSaving method:

public void transferToSaving(double amount) throws
   InsufficientBalanceException  {

   checkingBalance -= amount;
   savingBalance += amount;

   try {
      updateChecking(checkingBalance);
      if (checkingBalance < 0.00) {
         context.setRollbackOnly();
         throw new InsufficientBalanceException();
      }
      updateSaving(savingBalance);
   } catch (SQLException ex) {
       throw new EJBException 
          ("Transaction failed due to SQLException: " 
          + ex.getMessage());
   }
}

When the container rolls back a transaction, it always undoes the changes to data made by SQL calls within the transaction. However, only in entity beans will the container undo changes made to instance variables. (It does so by automatically invoking the entity bean's ejbLoad method, which loads the instance variables from the database.) When a rollback occurs, a session bean must explicitly reset any instance variables changed within the transaction. The easiest way to reset a session bean's instance variables is by implementing the SessionSynchronization interface.

 

Synchronizing a Session Bean's Instance Variables

The SessionSynchronization interface, which is optional, allows you to synchronize the instance variables with their corresponding values in the database. The container invokes the SessionSynchronization methods--afterBegin, beforeCompletion, and afterCompletion--at each of the main stages of a transaction.

The afterBegin method informs the instance that a new transaction has begun. The container invokes afterBegin immediately before it invokes the business method. The afterBegin method is a good place to load the instance variables from the database. The BankBean class, for example, loads the checkingBalance and savingBalance variables in the afterBegin method:

public void afterBegin() {

   System.out.println("afterBegin()");
   try {
      checkingBalance = selectChecking();
      savingBalance = selectSaving();
   } catch (SQLException ex) {
       throw new EJBException("afterBegin Exception: " +
           ex.getMessage());
   }
}

The container invokes the beforeCompletion method after the business method has finished, but just before the transaction commits. The beforeCompletion method is the last opportunity for the session bean to roll back the transaction (by calling setRollbackOnly). If it hasn't already updated the database with the values of the instance variables, the session bean may do so in the beforeCompletion method.

The afterCompletion method indicates that the transaction has completed. It has a single boolean parameter, whose value is true if the transaction was committed and false if it was rolled back. If a rollback occurred, the session bean can refresh its instance variables from the database in the afterCompletion method:

public void afterCompletion(boolean committed) {

   System.out.println("afterCompletion: " + committed);
   if (committed == false) {
      try {
         checkingBalance = selectChecking();
         savingBalance = selectSaving();
      } catch (SQLException ex) {
          throw new EJBException("afterCompletion SQLException:
         " + ex.getMessage());
      }
   }
}

 

Methods Not Allowed in Container-Managed Transactions

You should not invoke any method that might interfere with the transaction boundaries set by the container. The list of prohibited methods follows:

  • The commit, setAutoCommit, and rollback methods of java.sql.Connection
  • The getUserTransaction method of javax.ejb.EJBContext
  • Any method of javax.transaction.UserTransaction

You may, however, use these methods to set boundaries in bean-managed transactions.

 

Summary of Transaction Options for Enterprise Beans

If you're unsure about how to set up transactions in an enterprise bean, here's a tip: In the bean's deployment descriptor, specify container-managed transactions. Then, set the Required transaction attribute for the entire bean. This approach will work most of the time.

Table 14-2 lists the types of transactions that are allowed for the different types of enterprise beans. An entity bean must use container-managed transactions. With container-managed transactions, you specify the transaction attributes in the deployment descriptor and you roll back a transaction with the setRollbackOnly method of the EJBContext interface.

 

Table 14-2 Allowed Transaction Types for Enterprise Beans
Bean Type
Container-Managed
Bean-Managed
JTA
JDBC
Entity Y N N
Session Y Y Y
Message-driven Y Y Y

A session bean may have either container-managed or bean-managed transactions. There are two types of bean-managed transactions: JDBC and JTA transactions. You delimit JDBC transactions with the commit and rollback methods of the Connection interface. To demarcate JTA transactions, you invoke the begin, commit, and rollback methods of the UserTransaction interface.

In a session bean with bean-managed transactions, it is possible to mix JDBC and JTA transactions. This practice is not recommended, however, because it could make your code difficult to debug and maintain.

Like a session bean, a message-driven bean may have either container-managed or bean-managed transactions.

 

Transaction Timeouts

For container-managed transactions, you control the transaction timeout interval by setting the value of the transaction.timeout property in the default.properties file, which is in the config directory of your J2EE SDK installation. For example, you would set the timeout value to 5 seconds as follows:

transaction.timeout=5

With this setting, if the transaction has not completed within 5 seconds, the EJB container rolls it back.

When the J2EE SDK is first installed, the timeout value is set to 0:

transaction.timeout=0

If the value is 0, the transaction will not time out.

Only enterprise beans with container-managed transactions are affected by the transaction.timeout property. For enterprise beans with bean-managed JTA transactions, you invoke the setTransactionTimeout method of the UserTransaction interface.

 

Isolation Levels

Transactions not only ensure the full completion (or rollback) of the statements that they enclose but also isolate the data modified by the statements. The isolation level describes the degree to which the data being updated is visible to other transactions.

Suppose that a transaction in one program updates a customer's phone number, but before the transaction commits another program reads the same phone number. Will the second program read the updated and uncommitted phone number or will it read the old one? The answer depends on the isolation level of the transaction. If the transaction allows other programs to read uncommitted data, performance may improve because the other programs don't have to wait until the transaction ends. But there's a trade-off--if the transaction rolls back, another program might read the wrong data.

You cannot modify the isolation level of entity beans with container-managed persistence. These beans use the default isolation level of the DBMS, which is usually READ_COMMITTED.

For entity beans with bean-managed persistence and for all session beans, you can set the isolation level programmatically with the API provided by the underlying DBMS. A DBMS, for example, might allow you to permit uncommitted reads by invoking the setTransactionIsolation method:

Connection con;
... 
con.setTransactionIsolation(TRANSACTION_READ_UNCOMMITTED);

Do not change the isolation level in the middle of a transaction. Usually, such a change causes the DBMS software to issue an implicit commit. Because the isolation levels offered by DBMS vendors may vary, you should check the DBMS documentation for more information. Isolation levels are not standardized for the J2EE platform.

 

Updating Multiple Databases

The J2EE transaction manager controls all enterprise bean transactions except for bean-managed JDBC transactions. The J2EE transaction manager allows an enterprise bean to update multiple databases within a transaction. The figures that follow show two scenarios for updating multiple databases in a single transaction.

In Figure 14-2, the client invokes a business method in Bean-A. The business method begins a transaction, updates Database X, updates Database Y, and invokes a business method in Bean-B. The second business method updates Database Z and returns control to the business method in Bean-A, which commits the transaction. All three database updates occur in the same transaction.

Figure 14-2 Updating Multiple Databases

In Figure 14-3, the client calls a business method in Bean-A, which begins a transaction and updates Database X. Then, Bean-A invokes a method in Bean-B, which resides in a remote J2EE server. The method in Bean-B updates Database Y. The transaction managers of the J2EE servers ensure that both databases are updated in the same transaction.

Figure 14-3 Updating Multiple Databases across J2EE Servers

 

Transactions in Web Components

You may demarcate a transaction in a Web component with either the java.sql.Connection or javax.transaction.UserTransaction interface. These are the same interfaces that a session bean with bean-managed transactions may use. Transactions demarcated with the Connection interface are discussed in the section JDBC Transactions and those with the UserTransaction interface are discussed in the section JTA Transactions. For an example of a Web component using transactions, see Accessing Databases.