home | O'Reilly's CD bookshelfs | FreeBSD | Linux | Cisco | Cisco Exam  


CONTENTS

Chapter 14. Transactions

14.1 ACID Transactions

To understand how transactions work, we will revisit the TravelAgent EJB, a stateful session bean that encapsulates the process of making a cruise reservation for a customer.

In EJB 2.0, the TravelAgent EJB's bookPassage() method looks like this:

public TicketDO bookPassage(CreditCardDO card, double price)
    throws IncompleteConversationalState {
                   
    if (customer == null || cruise == null || cabin == null) {
        throw new IncompleteConversationalState();
    }
    try {
        ReservationHomeLocal resHome = (ReservationHomeLocal)
            jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal");
        ReservationLocal reservation =
            resHome.create(customer, cruise, cabin, price);
        Object ref = jndiContext.lookup
            ("java:comp/env/ejb/ProcessPaymentHomeRemote");
        ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote)
            PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class);
        ProcessPaymentRemote process = ppHome.create();
        process.byCredit(customer, card, price);

        TicketDO ticket = new TicketDO(customer,cruise,cabin,price);

        return ticket;
    } catch(Exception e) {
        throw new EJBException(e);
    }
}

In EJB 1.1, the bookPassage() method looks like this:

public TicketDO bookPassage(CreditCardDO card, double price)
    throws IncompleteConversationalState {
                   
    if (customer == null || cruise == null || cabin == null) {
        throw new IncompleteConversationalState();
    }
    try {
        ReservationHomeRemote resHome = (ReservationHomeRemote) 
            getHome("ReservationHomeRemote", ReservationHomeRemote.class);
        ReservationRemote reservation =
            resHome.create(customer, cruise, cabin, price);
        ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) 
            getHome("ProcessPaymentHomeRemote", ProcessPaymentHomeRemote.class);
        ProcessPaymentRemote process = ppHome.create();
        process.byCredit(customer, card, price);

        TicketDO ticket = new TicketDO(customer,cruise,cabin,price);

        return ticket;
    } catch(Exception e) {
        throw new EJBException(e);
    }
}

The TravelAgent EJB is a fairly simple session bean, and its use of other EJBs is a typical example of business-object design and workflow. Unfortunately, good business-object design is not enough to make these EJBs useful in an industrial-strength application. The problem is not with the definition of the EJBs or the workflow; the problem is that a good design does not, in and of itself, guarantee that the TravelAgent EJB's bookPassage() method represents a good transaction. To understand why, we will take a closer look at what a transaction is and what criteria a transaction must meet to be considered reliable.

In business, a transaction usually involves an exchange between two parties. When you purchase an ice cream cone, you exchange money for food; when you work for a company, you exchange skill and time for money (which you use to buy more ice cream). When you are involved in these exchanges, you monitor the outcome to ensure that you don't get "ripped off." If you give the ice cream vendor a $20 bill, you don't want him to drive off without giving you your change; likewise, you want to make sure that your paycheck reflects all the hours you worked. By monitoring these commercial exchanges, you are attempting to ensure the reliability of the transactions; you are making sure that each transaction meets everyone's expectations.

In business software, a transaction embodies the concept of a commercial exchange. A business system transaction (transaction for short) is the execution of a unit-of-work that accesses one or more shared resources, usually databases. A unit-of-work is a set of activities that relate to each other and must be completed together. The reservation process is a unit-of-work made up of several activities: recording a reservation, debiting a credit card, and generating a ticket together make up a unit-of-work.

Transactions are part of many different types of systems. In each transaction, the objective is the same: to execute a unit-of-work that results in a reliable exchange.

Here are some examples of other types of business systems that employ transactions:

ATM

The ATM (automatic teller machine) you use to deposit, withdraw, and transfer funds executes these units-of-work as transactions. In an ATM withdrawal, for example, the ATM checks to make sure you don't overdraw and then debits your account and spits out some money.

Online book order

You've probably purchased many of your Java books—maybe even this book—from an online bookseller. This type of purchase is also a unit-of-work that takes place as a transaction. In an online book purchase, you submit your credit card number, it is validated, and a charge is made for price of the book. Then an order to ship you the book is sent to the bookseller's warehouse.

Medical system

In a medical system, important data—some of it critical—is recorded about patients every day, including information about clinical visits, medical procedures, prescriptions, and drug allergies. The doctor prescribes the drug, then the system checks for allergies, contraindications, and appropriate dosages. If all tests pass, the drug can be administered. The tasks just described make up a unit-of-work in a medical system. A unit-of-work in a medical system may not be financial, but it's just as important. A failure to identify a drug allergy in a patient could be fatal.

As you can see, transactions are often complex and usually involve the manipulation of a lot of data. Mistakes in data can cost money, or even a life. Transactions must therefore preserve data integrity, which means that the transaction must work perfectly every time or not be executed at all. This is a pretty tall order, especially for complex systems. As difficult as this requirement is, however, when it comes to commerce there is no room for error. Units-of-work involving money or anything of value always require the utmost reliability, because errors impact the revenues and the well-being of the parties involved.

To give you an idea of the accuracy required by transactions, think about what would happen if a transactional system suffered from seemingly infrequent errors. ATMs provide customers with convenient access to their bank accounts and represent a significant percentage of the total transactions in personal banking. The transactions handled by ATMs are simple but numerous, providing us with a great example of why transactions must be error-proof. Let's say that a bank has 100 ATMs in a metropolitan area, and each ATM processes 300 transactions (deposits, withdrawals, or transfers) a day, for a total of 30,000 transactions per day. If each transaction, on average, involves the deposit, withdrawal, or transfer of about $100, about three million dollars will move through the ATM system per day. In the course of a year, that's a little over a billion dollars:

365 days x 100 ATMs x 300 transactions x $100.00 = $1,095,000,000.00

How well do the ATMs have to perform to be considered reliable? For the sake of argument, let's say that ATMs execute transactions correctly 99.99% of the time. This seems to be more than adequate: after all, only one out of every ten thousand transactions executes incorrectly. But over the course of a year, if you do the math, that could result in over $100,000 in errors!

$1,095,000,000.00 x .01% = $109,500.00

Obviously, this is an oversimplification of the problem, but it illustrates that even a small percentage of errors is unacceptable in high-volume or mission-critical systems. For this reason, experts in the field of transaction services have identified four characteristics of a transaction that must be met for a system to be considered safe. Transactions must be atomic, consistent, isolated, and durable (ACID)—the four horsemen of transaction services. Here's what each term means:

Atomic

To be atomic, a transaction must execute completely or not at all. This means that every task within a unit-of-work must execute without error. If any of the tasks fails, the entire unit-of-work or transaction is aborted, meaning that any changes to the data are undone. If all the tasks execute successfully, the transaction is committed, which means that the changes to the data are made permanent or durable.

Consistent

Consistency is a transactional characteristic that must be enforced by both the transactional system and the application developer. Consistency refers to the integrity of the underlying data store. The transactional system fulfills its obligation for consistency by ensuring that a transaction is atomic, isolated, and durable. The application developer must ensure that the database has appropriate constraints (primary keys, referential integrity, and so forth) and that the unit-of-work, the business logic, doesn't result in inconsistent data (i.e., data that is not in harmony with the real world it represents). In an account transfer, for example, a debit to one account must equal the credit to another account.

Isolated

A transaction must be allowed to execute without interference from other processes or transactions. In other words, the data that a transaction accesses cannot be affected by any other part of the system until the transaction or unit-of-work is completed.

Durable

Durability means that all the data changes made during the course of a transaction must be written to some type of physical storage before the transaction is successfully completed. This ensures that the changes are not lost if the system crashes.

To get a better idea of what these principles mean, we will examine the TravelAgent EJB in terms of the four ACID properties.

14.1.1 Is the TravelAgent EJB Atomic?

Our first measure of the TravelAgent EJB's reliability is its atomicity: does it ensure that the transaction executes completely or not at all? What we are really concerned with are the critical tasks that change or create information. In the bookPassage() method, a Reservation EJB is created, the ProcessPayment EJB debits a credit card, and a TicketDO object is created. All of these tasks must be successful for the entire transaction to be successful.

To understand the importance of the atomic characteristic, you have to imagine what would happen if even one of the subtasks failed to execute. If, for example, the creation of a Reservation EJB failed but all other tasks succeeded, your customer would probably end up getting bumped from the cruise or sharing the cabin with a stranger. As far as the travel agent is concerned, the bookPassage() method executed successfully because a TicketDO was generated. If a ticket is generated without the creation of a reservation, the state of the business system becomes inconsistent with reality, because the customer paid for a ticket but the reservation was not recorded. Likewise, if the ProcessPayment EJB fails to charge the customer's credit card, the customer gets a free cruise. He may be happy, but management won't be. Finally, if the TicketDO is never created, the customer will have no record of the transaction and probably will not be allowed onto the ship.

So the only way bookPassage() can be completed is if all the critical tasks execute successfully. If something goes wrong, the entire process must be aborted. Aborting a transaction requires more than simply not finishing the tasks; in addition, all the tasks that did execute within the transaction must be undone. If, for example, the creation of the Reservation EJB and ProcessPayment.byCredit() method succeeded but the creation of the TicketDO failed, throwing an exception from the constructor, the reservation record and payment records must not be added to the database.

14.1.2 Is the TravelAgent EJB Consistent?

In order for a transaction to be consistent, the business system must make sense after the transaction has completed. In other words, the state of the business system must be consistent with the reality of the business. This requires that the transaction enforce the atomic, isolated, and durable characteristics of the transaction, and it also requires diligent enforcement of integrity constraints by the application developer. If, for example, the application developer fails to include the credit card charge operation in the bookPassage() method, the customer will be issued a ticket but will never be charged. The data will be inconsistent with the expectation of the business—a customer should be charged for passage.

In addition, the database must be set up to enforce integrity constraints. For example, it should not be possible for a record to be added to the RESERVATION table unless the CABIN_ID, CRUISE_ID, and CUSTOMER_ID foreign keys map to corresponding records in the CABIN, CRUISE, and CUSTOMER tables, respectively. If a CUSTOMER_ID that does not map to a CUSTOMER record is used, referential integrity should cause the database to throw an error message.

14.1.3 Is the TravelAgent EJB Isolated?

If you are familiar with the concept of thread synchronization in Java or row-locking schemes in relational databases, isolation will be a familiar concept. To be isolated, a transaction must protect the data it is accessing from other transactions. This is necessary to prevent other transactions from interacting with data that is in transition. In the TravelAgent EJB, the transaction is isolated to prevent other transactions from modifying the EJBs that are being updated. Imagine the problems that would arise if separate transactions were allowed to change any entity bean at any time—transactions would walk all over each other. You could easily have several customers book the same cabin because their travel agents happened to make their reservations at the same time.

The isolation of data accessed by EJBs does not mean that the entire application shuts down during a transaction. Only those entity beans and data directly affected by the transaction are isolated. In the TravelAgent EJB, for example, the transaction isolates only the Reservation EJB created. There can be many Reservation EJBs in existence; there's no reason these other EJBs can't be accessed by other transactions.

14.1.4 Is the TravelAgent EJB Durable?

To be durable, the bookPassage() method must write all changes and new data to a permanent data store before it can be considered successful. While this may seem like a no-brainer, often it is not what happens in real life. In the name of efficiency, changes are often maintained in memory for long periods of time before being saved on a disk drive. The idea is to reduce disk accesses—which slow systems down—and only periodically write the cumulative effect of data changes. While this approach is great for performance, it is also dangerous because data can be lost when the system goes down and memory is wiped out. Durability requires the system to save all updates made within a transaction as the transaction successfully completes, thus protecting the integrity of the data.

In the TravelAgent EJB, this means that the new RESERVATION and PAYMENT records inserted are made persistent before the transaction can complete successfully. Only when the data is made durable are those specific records accessible through their respective EJBs from other transactions. Hence, durability also plays a role in isolation. A transaction is not finished until the data is successfully recorded.

Ensuring that transactions adhere to the ACID principles requires careful design. The system has to monitor the progress of a transaction to ensure that it does all its work, that the data is changed correctly, that transactions do not interfere with each other, and that the changes can survive a system crash. Engineering all this functionality into a system is a lot of work, and not something you would want to reinvent for every business system on which you work. Fortunately, EJB is designed to support transactions automatically, making the development of transactional systems easier. The rest of this chapter examines how EJB supports transactions implicitly (through declarative transaction attributes) and explicitly (through the Java Transaction API).

14.2 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. This involves the use of fairly complex APIs like the OMG's Object Transaction Service (OTS) or its Java implementation, the Java Transaction Service ( JTS). Explicit demarcation is difficult for developers to use at best, particularly if you are new to transactional systems. In addition, explicit transaction demarcation requires that the transactional code be written within the business logic, which reduces the clarity of the code and, more importantly, creates inflexible distributed objects. Once transaction demarcation is hardcoded into the business object, changes in transaction behavior require changes to the business logic itself. 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 deployment descriptor, which sets 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. In addition, an EJB deployed in one application can be defined with different transactional behavior than the same EJB deployed in a different application. Declarative transaction management reduces the complexity of transactions for EJB developers and application developers and makes it easier to create robust transactional applications.

14.2.1 Transaction Scope

Transaction scope is a crucial concept for understanding transactions. In this context, transaction scope means those EJBs—both session and entity—that 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 newly created Reservation EJB 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; 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, Reservation, and ProcessPayment EJBs—every EJB 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.

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 either implicitly using the EJB's transaction attributes or explicitly using the Java Transaction API ( JTA).

14.2.2 Transaction Attributes

As an application developer, you do not normally need to control transactions explicitly when using an EJB server. EJB servers can manage transactions implicitly, based on the transaction attributes established for EJBs at deployment time. The ability to specify how business objects participate in transactions through attribute-based programming is a common characteristic of CTMs, and one of the most important features of the EJB component model.

When an EJB is deployed, you can set its runtime transaction attribute in the deployment descriptor to one of several values. The following list shows the XML attribute values used to specify these transaction attributes:

  • NotSupported

  • Supports

  • Required

  • RequiresNew

  • Mandatory

  • Never

Using transaction attributes simplifies building transactional applications by reducing the risks associated with improper use of transactional protocols such as JTA (discussed later in this chapter). It's more efficient and easier to use transaction attributes than to control transactions explicitly.

You can set a transaction attribute for the entire EJB (in which case it applies to all methods) or to 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 fragments in the following sections show how to set the default transaction attribute of an EJB in the EJB's deployment descriptor.

14.2.2.1 Setting a transaction attribute

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

<ejb-jar>
    ...
    <assembly-descriptor>
        ...
        <container-transaction>
            <method>
                <ejb-name>TravelAgentEJB</ejb-name>
                <method-name> * </method-name>
            </method>
            <trans-attribute>Required</trans-attribute>
        </container-transaction>
        <container-transaction>
            <method>
                <ejb-name>TravelAgentEJB</ejb-name>
                <method-name>listAvailableCabins</method-name>
            </method>
            <trans-attribute>Supports</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 the transaction attribute that should be applied to that method. The first <container-transaction> element specifies that all methods by default have a transaction attribute of Required; 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 listAvailableCabins() method will have a Supports 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.

14.2.2.2 Transaction attributes defined

Here are the definitions of the transaction attributes listed earlier. In a few of the definitions, we say that the client transaction is suspended . This means that 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 any of the EJBs it calls. Once the method on the NotSupported EJB is done, the original transaction resumes its execution.

Figure 14-1 shows that a NotSupported EJB does not propagate the client transaction when one of its methods is invoked.

Figure 14-1. NotSupported attribute

figs/ejb3_1401.gif

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.

Figure 14-2(a) shows the Supports EJB being invoked by a transactional client and propagating the transaction. Figure 14-2(b) shows the Supports EJB being invoked by a nontransactional client.

Figure 14-2. Supports attribute

figs/ejb3_1402.gif

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.

Figure 14-3(a) shows the Required EJB being invoked by a transactional client and propagating the transaction.

Figure 14-3(b) shows the Required EJB being invoked by a nontransactional client, which causes it to start its own transaction.

Figure 14-3. Required attribute

figs/ejb3_1403.gif

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 RequiresNewEJB is done, the new transaction's scope ends and the original transaction resumes.

Figure 14-4(a) shows the RequiresNew EJB being invoked by a transactional client. The client's transaction is suspended while the EJB executes under its own transaction. Figure 14-4(b) shows the RequiresNew EJB being invoked by a nontransactional client; the RequiresNew EJB executes under its own transaction.

Figure 14-4. RequiresNew attribute

figs/ejb3_1404.gif

Mandatory

This attribute means that the enterprise bean method must always be made part of the transaction scope of the calling client. If the calling client or EJB is not part of a transaction, the invocation will fail, throwing a javax.transaction.TransactionRequiredException to remote clients or a javax.ejb.TransactionRequiredLocalException to local EJB 2.0 clients.

Figure 14-5(a) shows the Mandatory EJB being invoked by a transactional client and propagating the transaction. Figure 14-5(b) shows the Mandatory EJB being invoked by a nontransactional client; the method throws a TransactionRequiredException to remote clients or a TransactionRequredLocalException to local EJB 2.0 clients, because there is no transaction scope.

Figure 14-5. Mandatory attribute

figs/ejb3_1405.gif

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 a RemoteException to remote clients or an EJBException to local EJB 2.0 clients. However, if the calling client or EJB is not involved in a transaction the Never EJB will execute normally without a transaction.

Figure 14-6(a) shows the Never EJB being invoked by a nontransactional client. Figure 14-6(b) shows the Never EJB being invoked by transactional client; the method throws a RemoteException to remote clients or an EJBException to local EJB 2.0 clients, because the method can never be invoked by a client or EJB that is included in a transaction.

Figure 14-6. Never attribute

figs/ejb3_1406.gif

14.2.2.3 EJB 2.0: Container-managed persistence and transaction attributes

The EJB 2.0 specification strongly advises that CMP 2.0 entity beans 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. While the specification requires that these three transaction attributes be supported for CMP 2.0, support for the Never, Supports, and NotSupported transaction attributes is optional. If a vendor wishes to support these attributes (which allow the bean to execute without a transaction) they may do so, but it's not recommended. Consult your vendor's documentation to determine if they support the optional transaction attributes. This book recommends that you use only Required, RequiresNew, or Mandatory with EJB 2.0 container-managed persistence entity beans.

14.2.2.4 EJB 2.0: Message-driven beans and transaction attributes

Message-driven beans may declare only the NotSupported or Required transaction attributes. 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.

14.2.3 Transaction Propagation

To illustrate the impact of transaction attributes on enterprise bean methods, we'll look once again at the bookPassage() method of the TravelAgent EJB created in Chapter 12 (see the listings earlier in this chapter).

In order for bookPassage() to execute as a successful transaction, both the creation of the Reservation EJB and the charge to the customer must be successful. This means that 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 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 EJB cannot be created, 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. Explicit 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 = agentHome.create(customer);
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 that 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 is thrown by the bookPassage() method, the transaction is automatically rolled back. We will 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 EJB is created, it is also registered with the transaction manager under the same transaction. When all the EJBs 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 reports an error or fails, any changes made by either the ProcessPayment or Reservation EJB are rolled back by the transaction manager. Figure 14-7 illustrates the propagation and management of the TravelAgent EJB's transactional context.

Figure 14-7. Managing the TravelAgent EJB's transactional context

figs/ejb3_1407.gif

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 EJB server than the TravelAgent EJB, the two EJB servers would cooperate to manage the transaction as one unit-of-work. This is called a distributed transaction.[1]

A distributed transaction is a great deal more complicated, requiring what is called a two-phase commit (2-PC or TPC). 2-PC is a mechanism that 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.

14.2.4 EJB 2.0: Collection-Based Relationships and Transactions

In EJB 2.0 container-managed persistence, collection-based relationships may only be accessed within a single transaction. In other words, it's illegal to obtain a Collection object from a collection-based relationship field in one transaction and then use it in another.

For example, if an enterprise bean accesses another's collection-based relationship field through its local interface, the Collection returned from the accessor method can be used only within the same transaction:

public class HypotheticalBean implements javax.ejb.EntityBean {

    public void methodX(CustomerLocal customer) {

        Collection reservations = customer.getReservations();
        Iterator iterator = reservations.iterator;
        while(iterator.hasNext()){
            ...
        ...
   }
...
}

If the Customer EJB's getReservations() method was declared with a transaction attribute of RequiresNew, attempting to invoke any methods on the Collection, including the iterator() method, will result in a java.lang.IllegalStateException. This exception is thrown because the Collection object was created within the scope of the getReservations() transaction, not in the scope of methodX()'s transaction. The transaction context of methodX() is different from the transaction context of the getReservations() method.

The Collection from an entity bean can be used by another co-located bean only if it is obtained and accessed in the same transaction context. As long as the Customer EJB's getReservations() method propagates the transaction context of methodX(), the Collection can be used without any problems. This can be accomplished by changing the getReservations() method so that it declares its transaction attribute as Required or Mandatory.

14.3 Isolation and Database Locking

Transaction isolation (the "I" in ACID) is a critical part of any transactional system. This section explains isolation conditions, database locking, and transaction isolation levels. These concepts are important when deploying any transactional system.

14.3.1 Dirty, Repeatable, and Phantom Reads

Transaction isolation is defined in terms of isolation conditions called dirty reads, repeatable reads, and phantom reads. These conditions describe what can happen when two or more transactions operate on the same data.[2]

To illustrate these conditions, let's think about two separate client applications using their own instances of the TravelAgent EJB to access the same data—specifically, a cabin record with the primary key of 99. These examples revolve around the RESERVATION table, which is accessed by both the bookPassage() method (through the Reservation EJB) and the listAvailableCabins() method (through JDBC). It might be a good idea to go back to Chapter 12 and review how the RESERVATION table is accessed through these methods. This will help you to understand how two transactions executed by two different clients can impact each other. Assume that both methods have a transaction attribute of Required.

14.3.1.1 Dirty reads

A dirty read occurs when the first transaction reads uncommitted changes made by a second transaction. If the second transaction is rolled back, the data read by the first transaction becomes invalid because the rollback undoes the changes. The first transaction will not be aware that the data it has read has become invalid. Here's a scenario showing how a dirty read can occur (illustrated in Figure 14-8):

  1. Time 10:00:00: Client 1 executes the TravelAgent.bookPassage() method. Along with the Customer and Cruise EJBs, Client 1 had previously chosen Cabin 99 to be included in the reservation.

  2. Time 10:00:01: Client 1's TravelAgent EJB creates a Reservation EJB within the bookPassage() method. The Reservation EJB's create() method inserts a record into the RESERVATION table, which reserves Cabin 99.

  3. Time 10:00:02: Client 2 executes TravelAgent.listAvailableCabins(). Cabin 99 has been reserved by Client 1, so it is not in the list of available cabins that is returned from this method.

  4. Time 10:00:03: Client 1's TravelAgent EJB executes the ProcessPayment.byCredit() method within the bookPassage() method. The byCredit() method throws an exception because the expiration date on the credit card has passed.

  5. Time 10:00:04: The exception thrown by the ProcessPayment EJB causes the entire bookPassage() transaction to be rolled back. As a result, the record inserted into the RESERVATION table when the Reservation EJB was created is not made durable (i.e., it is removed). Cabin 99 is now available.

    Figure 14-8. A dirty read

    figs/ejb3_1408.gif

    Client 2 is now using an invalid list of available cabins because Cabin 99 is available but is not included in the list. This omission would be serious if Cabin 99 was the last available cabin, because Client 2 would inaccurately report that the cruise was booked. The customer would presumably try to book a cruise on a competing cruise line.

14.3.1.2 Repeatable reads

A repeatable read is when the data read is guaranteed to look the same if read again during the same transaction. Repeatable reads are guaranteed in one of two ways: either the data read is locked against changes or the data read is a snapshot that doesn't reflect changes. If the data is locked, it cannot be changed by any other transaction until the current transaction ends. If the data is a snapshot, other transactions can change the data, but these changes will not be seen by this transaction if the read is repeated. Here's an example of a repeatable read (illustrated in Figure 14-9):

  1. Time 10:00:00: Client 1 begins an explicit javax.transaction.UserTransaction.

  2. Time 10:00:01: Client 1 executes TravelAgent.listAvailableCabins(2), asking for a list of available cabins that have two beds. Cabin 99 is in the list of available cabins.

  3. Time 10:00:02: Client 2 is working with an interface that manages Cabin EJBs. Client 2 attempts to change the bed count on Cabin 99 from 2 to 3.

  4. Time 10:00:03: Client 1 re-executes TravelAgent.listAvailableCabins(2). Cabin 99 is still in the list of available cabins.

    Figure 14-9. Repeatable read

    figs/ejb3_1409.gif

    This example is somewhat unusual because it uses javax.transaction.UserTransaction. This class is covered in more detail later in this chapter; essentially, it allows a client application to control the scope of a transaction explicitly. In this case, Client 1 places transaction boundaries around both calls to listAvailableCabins(), so that they are a part of the same transaction. If Client 1 didn't do this, the two listAvailableCabins() methods would have executed as separate transactions and our repeatable read condition would not have occurred.

    Although Client 2 attempted to change the bed count for Cabin 99 to 3, Cabin 99 still shows up in the Client 1 call to listAvailableCabins() when a bed count of 2 is requested. This is because either Client 2 was prevented from making the change (because of a lock) or Client 2 was able to make the change, but Client 1 is working with a snapshot of the data that doesn't reflect that change.

    A nonrepeatable read is when the data retrieved in a subsequent read within the same transaction can return different results. In other words, the subsequent read can see the changes made by other transactions.

14.3.1.3 Phantom reads

Phantom reads occur when new records added to the database are detectable by transactions that started prior to the insert. Queries will include records added by other transactions after their transaction has started. Here's a scenario that includes a phantom read (illustrated in Figure 14-10):

  1. Time 10:00:00: Client 1 begins an explicit javax.transaction.UserTransaction.

  2. Time 10:00:01: Client 1 executes TravelAgent.listAvailableCabins(2), asking for a list of available cabins that have two beds. Cabin 99 is in the list of available cabins.

  3. Time 10:00:02: Client 2 executes bookPassage() and creates a Reservation EJB. The reservation inserts a new record into the RESERVATION table, reserving cabin 99.

  4. Time 10:00:03: Client 1 re-executes TravelAgent.listAvailableCabins(2). Cabin 99 is no longer in the list of available cabins.

    Figure 14-10. Phantom read

    figs/ejb3_1410.gif

    Client 1 places transaction boundaries around both calls to listAvailableCabins(), so that they are part of the same transaction. In this case, the reservation was made between the listAvailableCabins() queries in the same transaction. Therefore, the record inserted in the RESERVATION table did not exist when the first listAvailableCabins() method was invoked, but it did exist and was visible when the second listAvailableCabins() method was invoked. The record inserted is a phantom record.

14.3.2 Database Locks

Databases, especially relational databases, normally use several different locking techniques. The most common are read locks, write locks, and exclusive write locks. (I've taken the liberty of adding "snapshots," although this isn't a formal term.) These locking mechanisms control how transactions access data concurrently. Locking mechanisms impact the read conditions I just described. These types of locks are simple concepts that are not directly addressed in the EJB specification. Database vendors implement these locks differently, so you should understand how your database addresses these locking mechanisms to best predict how the isolation levels described in this section will work.

The four types of lock are:

Read locks

Read locks prevent other transactions from changing data read during a transaction until the transaction ends, thus preventing nonrepeatable reads. Other transactions can read the data but not write to it. The current transaction is also prohibited from making changes. Whether a read lock locks only the records read, a block of records, or a whole table depends on the database being used.

Write locks

Write locks are used for updates. A write lock prevents other transactions from changing the data until the current transaction is complete but allows dirty reads by other transactions and by the current transaction itself. In other words, the transaction can read its own uncommitted changes.

Exclusive write locks

Exclusive write locks are used for updates. An exclusive write lock prevents other transactions from reading or changing the data until the current transaction is complete. An exclusive write lock prevents dirty reads by other transactions. Other transactions are not allowed to read the data while it is exclusively locked. Some databases do not allow transactions to read their own data while it is exclusively locked.

Snapshots

Some databases get around locking by providing every transaction with its own snapshot of the data. A snapshot is a frozen view of the data that is taken when the transaction begins. Snapshots can prevent dirty reads, nonrepeatable reads, and phantom reads. They can be problematic because the data is not real-time; it is old the instant the snapshot is taken.

14.3.3 Transaction Isolation Levels

Transaction isolation is defined in terms of the isolation conditions (dirty reads, repeatable reads, and phantom reads). Isolation levels are commonly used in database systems to describe how locking is applied to data within a transaction.[3] The following terms are usually used to discuss isolation levels:

Read Uncommitted

The transaction can read uncommitted data (i.e., data changed by a different transaction that is still in progress).

Dirty reads, nonrepeatable reads, and phantom reads can occur. Bean methods with this isolation level can read uncommitted changes.

Read Committed

The transaction cannot read uncommitted data; data that is being changed by a different transaction cannot be read.

Dirty reads are prevented; nonrepeatable reads and phantom reads can occur. Bean methods with this isolation level cannot read uncommitted data.

Repeatable Read

The transaction cannot change data that is being read by a different transaction.

Dirty reads and nonrepeatable reads are prevented; phantom reads can occur. Bean methods with this isolation level have the same restrictions as Read Committed and can execute only repeatable reads.

Serializable

The transaction has exclusive read and update privileges to data; different transactions can neither read nor write to the same data.

Dirty reads, nonrepeatable reads, and phantom reads are prevented. This isolation level is the most restrictive.

These isolation levels are the same as those defined for JDBC. Specifically, they map to the static final variables in the java.sql.Connection class. The behavior modeled by the isolation levels in the connection class is the same as the behavior described here.

The exact behavior of these isolation levels depends largely on the locking mechanism used by the underlying database or resource. How the isolation levels work depends in large part on how your database supports them.

In EJB, the deployer sets transaction isolation levels in a vendor-specific way if the container manages the transaction. The EJB developer sets the transaction isolation level if the enterprise bean manages its own transactions. Up to this point we have discussed only container-managed transactions; we will discuss bean-managed transactions later in this chapter.

14.3.4 Balancing Performance Against Consistency

Generally speaking, as the isolation levels become more restrictive, the performance of the system decreases because more restrictive isolation levels prevent transactions from accessing the same data. If isolation levels are very restrictive, like Serializable, then all transactions, even simple reads, must wait in line to execute. This can result in a system that is very slow. EJB systems that process a large number of concurrent transactions and need to be very fast will therefore avoid the Serializableisolation level where it is not necessary, since it will be prohibitively slow.

Isolation levels, however, also enforce consistency of data. More restrictive isolation levels help ensure that invalid data is not used for performing updates. The old adage "garbage in, garbage out" applies here. The Serializable isolation level ensures that data is never accessed concurrently by transactions, thus ensuring that the data is always consistent.

Choosing the correct isolation level requires some research about the database you are using and how it handles locking. You must also balance the performance needs of your system against consistency. This is not a cut-and-dried process, because different applications use data differently.

Although there are only three ships in Titan's system, the entity beans that represent them are included in most of Titan's transactions. This means that many, possibly hundreds, of transactions will be accessing these Ship EJBs at the same time. Access to Ship EJBs needs to be fast or it becomes a bottleneck, so we do not want to use a restrictive isolation level. At the same time, the ship data also needs to be consistent; otherwise, hundreds of transactions will be using invalid data. Therefore, we need to use a strong isolation level when making changes to ship information. To accommodate these conflicting requirements, we can apply different isolation levels to different methods.

Most transactions use the Ship EJB's get methods to obtain information. This is read-only behavior, so the isolation level for the get methods can be very low, such as Read Uncommitted. The set methods of the Ship EJB are almost never used; the name of the ship probably will not change for years. However, the data changed by the set methods must be isolated to prevent dirty reads by other transactions, so we will use the most restrictive isolation level, Serializable, on the ship's set methods. By using different isolation levels on different business methods, we can balance consistency against performance.

14.3.4.1 Controlling isolation levels

Different EJB servers allow different levels of granularity for setting isolation levels; some servers defer this responsibility to the database. Most EJB servers control the isolation level through the resource access API (e.g., JDBC and JMS) and may allow different resources to have different isolation levels, but will generally require that access to the same resource within a single transaction use a consistent isolation level. You will need to consult your vendor's documentation to find out the level of control your server offers.

Bean-managed transactions in session beans (stateful and stateless) and message-driven beans (EJB 2.0), however, allow the EJB developer to specify the transaction isolation level using the API of the resource providing persistent storage. The JDBC API, for example, provides a mechanism for specifying the isolation level of the database connection. The following code shows how this is done. Bean-managed transactions are covered in more detail later in this chapter.

...
DataSource source = (javax.sql.DataSource)
    jndiCntxt.lookup("java:comp/env/jdbc/titanDB");

Connection con = source.getConnection();
con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
...

You can set the isolation level to be different for different resources within the same transaction, but all enterprise beans that use the same resource in a transaction should use the same isolation level.

14.4 Nontransactional Beans

Beans that reside outside a transaction scope normally provide some kind of stateless service that does not directly manipulate data in a data store. While these types of enterprise beans may be necessary as utilities during a transaction, they do not need to meet the stringent ACID requirements of a transaction.

Consider a nontransactional stateless session bean, the Quote EJB, that provides live stock quotes. This EJB may respond to a request from a transactional EJB involved in a stock purchase transaction. The success or failure of the stock purchase, as a transaction, will not impact the state or operations of the Quote EJB, so it does not need to be part of the transaction. Beans that are involved in transactions are subjected to the isolated ACID property, which means that their services cannot be shared during the life of the transaction. Making an enterprise bean transactional can be an expensive runtime activity. Declaring an EJB to be nontransactional (i.e., NotSupported) leaves it out of the transaction scope, which may improve the performance and availability of that service.

14.5 Explicit Transaction Management

Although this section covers JTA, it is strongly recommended that you do not attempt to manage transactions explicitly. Through transaction attributes, Enterprise JavaBeans provides a comprehensive and simple mechanism for delimiting transactions at the method level and propagating transactions automatically. Only developers with a thorough understanding of transactional systems should attempt to use JTA with EJB.

In EJB, implicit transaction management is provided on the enterprise bean method level so that we can define transactions that are delimited by the scope of the method being executed. This is one of the primary advantages of EJB over cruder distributed object implementations: it reduces complexity and therefore programmer error. In addition, declarative transaction demarcation, as used in EJB, separates the transactional behavior from the business logic; a change to transactional behavior does not require changes to the business logic. In rare situations, however, it may be necessary to take control of transactions explicitly. To do this, it is necessary to have a much more complete understanding of transactions.

Explicit management of transactions is complex and is normally accomplished using the OMG's Object Transaction Service (OTS) or the Java implementation of OTS, the Java Transaction Service ( JTS). OTS and JTS provide APIs that allow developers to work with transaction managers and resources (e.g., databases and JMS providers) directly. While the JTS implementation of OTS is robust and complete, it is not the easiest API to work with; it requires clean and intentional control over the bounds of enrollment in transactions.

Enterprise JavaBeans supports a much simpler API, the Java Transaction API ( JTA), for working with transactions. This API is implemented by the javax.transaction package. JTA actually consists of two components: a high-level transactional client interface and a low-level X/Open XA interface. We are concerned with the high-level client interface, since that is the one accessible to the enterprise beans and is the recommended transactional interface for client applications. The low-level XA interface is used by the EJB server and container to automatically coordinate transactions with resources such as databases.

As an application and EJB developer, your use of explicit transaction management will probably focus on one very simple interface: javax.transaction.UserTransaction. UserTransaction provides an interface to the transaction manager that allows the application developer to manage the scope of a transaction explicitly. Here is an example of how explicit demarcation might be used in an EJB or client application:

Object ref = getInitialContext().lookup("TravelAgentHomeRemote");
TravelAgentHome home = (TravelAgentHome)
    PortableRemoteObject.narrow(ref, TravelAgentHome.class);

TravelAgent tr1 = home.create(customer);
tr1.setCruiseID(cruiseID);
tr1.setCabinID(cabin_1);
TravelAgent tr2 = home.create(customer);
tr2.setCruiseID(cruiseID);
tr2.setCabinID(cabin_2);

javax.transaction.UserTransaction tran = ...; // Get the UserTransaction.
tran.begin();
tr1.bookPassage(visaCard,price);
tr2.bookPassage(visaCard,price);
tran.commit();

The client application needs to book two cabins for the same customer—in this case, the customer is purchasing a cabin for himself and his children. The customer does not want to book either cabin unless he can get both, so the client application is designed to include both bookings in the same transaction. Explicitly marking the transaction's boundaries through the use of the javax.transaction.UserTransaction object does this. Each enterprise bean method invoked by the current thread between the UserTransaction.begin() and UserTransaction.commit() methods is included in the same transaction scope, according to the transaction attributes of the enterprise bean methods invoked.

Obviously this example is contrived, but the point it makes is clear. Transactions can be controlled directly, instead of depending on method scope to delimit them. The advantage of using explicit transaction demarcation is that it gives the client control over the bounds of a transaction. The client, in this case, may be a client application or another enterprise bean.[4] In either case, the same javax.transaction.UserTransaction is used, but it is obtained from different sources depending on whether it is needed on the client or in an enterprise bean.

Java 2 Enterprise Edition ( J2EE) specifies how a client application can obtain a UserTransaction object using JNDI. Here's how a client obtains a UserTransaction object if the EJB container is part of a J2EE system ( J2EE and its relationship with EJB is covered in more detail in Chapter 17):

...
Context jndiCntx = new InitialContext();
UserTransaction tran = (UserTransaction)
    jndiCntx.lookup("java:comp/UserTransaction");
utx.begin();
... 
utx.commit();
...

Enterprise beans can also manage transactions explicitly. Only session beans and message-driven beans with the <transaction-type> value of Bean can manage their own transactions. Enterprise beans that manage their own transactions are frequently referred to as bean-managed transaction (BMT) beans. Entity beans can never be BMT beans. BMT beans do not declare transaction attributes for their methods. Here's how a session bean declares that it will manage transactions explicitly:

<ejb-jar>
    <enterprise-beans>
    ...
        <session>
        ...
        <transaction-type>Bean</transaction-type>
        ...

To manage its own transaction, an enterprise bean needs to obtain a UserTransaction object. An enterprise bean obtains a reference to the UserTransaction from the EJBContext, as shown here:

public class HypotheticalBean extends SessionBean {
    SessionContext ejbContext;
  
    public void someMethod() {
        try {
            UserTransaction ut = ejbContext.getUserTransaction();
            ut.begin();
    
            // Do some work.
    
            ut.commit();
    } catch(IllegalStateException ise) {...}
        catch(SystemException se) {...}
        catch(TransactionRolledbackException tre) {...}
        catch(HeuristicRollbackException hre) {...}
        catch(HeuristicMixedException hme) {...}

An enterprise bean can also access the UserTransaction from the JNDI ENC, as shown in the following example. Both methods are legal and proper. The enterprise bean performs the lookup using the "java:comp/env/UserTransaction" context:

InitialContext jndiCntx = new InitialContext();
UserTransaction tran = (UserTransaction)
    jndiCntx.lookup("java:comp/env/UserTransaction");

14.5.1 Transaction Propagation in Bean-Managed Transactions

With stateless session beans, transactions that are managed using the UserTransaction must be started and completed within the same method. In other words, UserTransaction transactions cannot be started in one method and ended in another. This makes sense because stateless session bean instances are shared across many clients; so while one stateless instance may service a client's first request, a completely different instance may service a subsequent request by the same client. With stateful session beans, however, a transaction can begin in one method and be committed in another because a stateful session bean is used by only one client. This allows a stateful session bean to associate itself with a transaction across several different client-invoked methods. As an example, imagine the TravelAgent EJB as a BMT bean. In the following code, the transaction is started in the setCruiseID() method and completed in the bookPassage() method. This allows the TravelAgent EJB's methods to be associated with the same transaction.

14.5.1.1 EJB 2.0: TravelAgentBean

The definition of the TravelAgentBean class looks like this in EJB 2.0:

import com.titan.reservation.*;

import java.sql.*;
import javax.sql.DataSource;
import java.util.Vector;
import java.rmi.RemoteException;
import javax.naming.NamingException;
import javax.ejb.EJBException;

public class TravelAgentBean implements javax.ejb.SessionBean {
    ...
    public void setCruiseID(Integer cruiseID)
        throws javax.ejb.FinderException { 
        try { 
            ejbContext.getUserTransaction().begin();
            CruiseHomeLocal home = (CruiseHomeLocal)
                jndiContext.lookup("java:comp/env/ejb/CruiseHome");
           
            cruise = home.findByPrimaryKey(cruiseID);
        } catch(RemoteException re) {
            throw new EJBException(re);
        }
        
    }
    public TicketDO bookPassage(CreditCardDO card, double price)
        throws IncompleteConversationalState {

        try {
            if (ejbContext.getUserTransaction().getStatus() != 
                javax.transaction.Status.STATUS_ACTIVE) {

                throw new EJBException("Transaction is not active");
            }

        } catch(javax.transaction.SystemException se) {
             throw new EJBException(se);
        }
                   
        if (customer == null || cruise == null || cabin == null) 
        {
            throw new IncompleteConversationalState();
        }
        try {
            ReservationHomeLocal resHome = (ReservationHomeLocal)
                jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal");
            
            ReservationLocal reservation =
                resHome.create(customer, cruise, cabin, price);
                
            Object ref = 
                jndiContext.lookup("java:comp/env/ejb/ProcessPaymentHomeRemote");

            ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote)
                PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class);
            
            ProcessPaymentRemote process = ppHome.create();
            process.byCredit(customer, card, price);

            TicketDO ticket = new TicketDO(customer,cruise,cabin,price);

            ejbContext.getUserTransaction().commit();

            return ticket;
        } catch(Exception e) {
            throw new EJBException(e);
        }
    }
    ...
} 
14.5.1.2 EJB 1.1: TravelAgentBean

In EJB 1.1, the TravelAgentBean class definition looks like this:

public class TravelAgentBean implements javax.ejb.SessionBean {
    ...
    public void setCruiseID(Integer cruiseID) throws javax.ejb.FinderException { 
        try { 
            ejbContext.getUserTransaction().begin();
            CruiseHomeRemote home = (CruiseHomeRemote)
                getHome("CruiseHome", CruiseHomeRemote. class);
            cruise = home.findByPrimaryKey(cruiseID);
        } catch(RemoteException re) {
            throw new EJBException(re);
        }
        
    }
    public TicketDO bookPassage(CreditCardDO card, double price)
        throws IncompleteConversationalState {

        try {
            if (ejbContext.getUserTransaction().getStatus() != 
                javax.transaction.Status.STATUS_ACTIVE) {
                throw new EJBException("Transaction is not active");
            }
        } catch(javax.transaction.SystemException se) {
            throw new EJBException(se);
        }
                   
        if (customer == null || cruise == null || cabin == null) {
            throw new IncompleteConversationalState();
        }
        try {
            ReservationHomeRemote resHome = (ReservationHomeRemote) 
                getHome("ReservationHomeRemote", ReservationHomeRemote.class);
            ReservationRemote reservation =
                resHome.create(customer, cruise, cabin, price);
            ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote)
                getHome("ProcessPaymentHomeRemote", ProcessPaymentHomeRemote.class);
            ProcessPaymentRemote process = ppHome.create();
            process.byCredit(customer, card, price);

            TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
            
            ejbContext.getUserTransaction().commit();
           
            return ticket;
        } catch(Exception e) {
            throw new EJBException(e);
        }
    }
    ...
} 

Repeated calls to the EJBContext.getUserTransaction() method return a reference to the same UserTransaction object. The container is required to retain the association between the transaction and the stateful bean instance across multiple client calls until the transaction terminates.

In the bookPassage() method, we can check the status of the transaction to ensure that it is still active. If the transaction is no longer active, we throw an exception. The use of the getStatus() method is covered in more detail later in this chapter.

When a bean-managed transaction method is invoked by a client that is already involved in a transaction, the client's transaction is suspended until the method returns. This suspension occurs regardless of whether the BMT bean explicitly started its own transaction within the method or the transaction was started in a previous method invocation. The client transaction is always suspended until the BMT method returns.

Transaction control across methods is strongly discouraged because it can result in improperly managed transactions and long-lived transactions that lock up resources.

14.5.1.3 EJB 2.0: Message-driven beans and bean-managed transactions

Message-driven beans also have the option of managing their own transactions. In the case of MDBs, the scope of the transaction must begin and end within the onMessage() method—it is not possible for a bean-managed transaction to span onMessage() calls.

You can transform the ReservationProcessor EJB into a BMT bean simply by changing its <transaction-type> value to Bean:

<ejb-jar>
    <enterprise-beans>
    ...
        <message-driven>
        ...
        <transaction-type>Bean</transaction-type>
        ...

In this case, the ReservationProcessorBean class would be modified to use the javax.transaction.UserTransaction to mark the beginning and end of the transaction in onMessage():

public class ReservationProcessorBean implements javax.ejb.MessageDrivenBean,
    javax.jms.MessageListener {

    MessageDrivenContext ejbContext;
    Context jndiContext;

    public void onMessage(Message message) {
        try {

            ejbContext.getUserTransaction().begin();

            MapMessage reservationMsg = (MapMessage)message;

            Integer customerPk = (Integer)reservationMsg.getObject("CustomerID");
            Integer cruisePk = (Integer)reservationMsg.getObject("CruiseID");
            Integer cabinPk = (Integer)reservationMsg.getObject("CabinID");
            double price = reservationMsg.getDouble("Price");

            //get the credit card
            Date expirationDate = 
                new Date(reservationMsg.getLong("CreditCardExpDate"));
            String cardNumber = reservationMsg.getString("CreditCardNum");
            String cardType = reservationMsg.getString("CreditCardType");

            CreditCardDO card = new CreditCardDO(cardNumber,expirationDate,cardType);
            CustomerRemote customer = getCustomer(customerPk);
            CruiseLocal cruise = getCruise(cruisePk);
            CabinLocal cabin = getCabin(cabinPk);

            ReservationHomeLocal resHome = (ReservationHomeLocal)
                jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal");
            ReservationLocal reservation =
                resHome.create(customer,cruise,cabin,price,new Date());

            Object ref = 
                jndiContext.lookup("java:comp/env/ejb/ProcessPaymentHomeRemote");
            ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote)
                PortableRemoteObject.narrow(ref,ProcessPaymentHomeRemote.class);
            ProcessPaymentRemote process = ppHome.create();

            process.byCredit(customer,card,price);
            TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
            deliverTicket(reservationMsg,ticket);

            ejbContext.getUserTransaction.commit();

        } catch(Exception e) {
            throw new EJBException(e);
        }
    }
    ...

It is important to understand that in BMT, the message consumed by the MDB is not part of the transaction. When an MDB uses container-managed transactions, the message it is handling is a part of the transaction, so if the transaction is rolled back, the consumption of the message is also rolled back, forcing the JMS provider to redeliver the message. But with bean-managed transactions, the message is not part of the transaction, so if the BMT transaction is rolled back, the JMS provider will not be aware of the transaction's failure. However, all is not lost, because the JMS provider can still rely on message acknowledgment to determine if the message was successfully delivered.

The EJB container will acknowledge the message if the onMessage() method returns successfully. If, however, a RuntimeException is thrown by the onMessage() method, the container will not acknowledge the message and the JMS provider will suspect a problem and will probably attempt to redeliver the message. If redelivery of a message is important when a transaction fails in BMT, your best course of action is to ensure that the onMessage() method throws an EJBException, so that the container will not acknowledge the message received from the JMS provider.

Vendors use proprietary (declarative) mechanisms to specify the number of times to redeliver messages to BMT/NotSupported MDBs that "fail" to acknowledge receipt. The JMS provider may provide a "dead message" area into which such messages will be placed if they cannot be successfully processed according to the retry count. The dead message area can be monitored by administrators, and delivered messages can be detected and handled manually.

Although the message is not part of the transaction, everything else between the UserTransaction.begin() and UserTransaction.commit() methods is part of the same transaction. This includes creating a new Reservation EJB and processing the credit card using the ProcessPayment EJB. If a transaction failure occurs, these operations will be rolled back. The transaction also includes the use of the JMS API in the deliverTicket() method to send the ticket message. If a transaction failure occurs, the ticket message will not be sent.

14.5.2 Heuristic Decisions

Transactions are normally controlled by a transaction manager (often the EJB server) that manages the ACID characteristics across several enterprise beans, databases, and servers. This transaction manager uses a two-phase commit (2-PC) to manage transactions. 2-PC is a protocol for managing transactions that commits updates in two stages. 2-PC is complex, but basically it requires that servers and databases cooperate through an intermediary, the transaction manager, to ensure that all the data is made durable together. Some EJB servers support 2-PC while others do not, and the value of this transaction mechanism is a source of some debate. The important point to remember is that a transaction manager controls the transaction; based on the results of a poll against the resources (databases, JMS providers, and other resources), it decides whether all the updates should be committed or rolled back. A heuristic decision is when one of the resources makes a unilateral decision to commit or roll back without permission from the transaction manager. Once a heuristic decision has been made, the atomicity of the transaction is lost and data-integrity errors can occur.

UserTransaction, discussed in the next section, throws a few different exceptions related to heuristic decisions; these are included in the following discussion.

14.5.3 UserTransaction

UserTransaction is a Java interface defined in the following code. EJB servers are not required to support the rest of JTA, nor are they required to use JTS for their transaction service. The UserTransaction is defined as follows:

public interface javax.transaction.UserTransaction {

    public abstract void begin() throws IllegalStateException, SystemException;
    public abstract void commit() throws IllegalStateException, SystemException, 
        TransactionRolledbackException, HeuristicRollbackException, 
        HeuristicMixedException;
    public abstract int getStatus();
    public abstract void rollback() throws IllegalStateException, SecurityException,
        SystemException;
    public abstract void setRollbackOnly() throws IllegalStateException, 
        SystemException;
    public abstract void setTransactionTimeout(int seconds) throws SystemException;

}

Here's what the methods defined in this interface do:

begin()

Invoking the begin() method creates a new transaction. The thread that executes the begin() method is immediately associated with the new transaction, which is then propagated to any EJB that supports existing transactions. The begin() method can throw one of two checked exceptions. An IllegalStateException is thrown when begin() is called by a thread that is already associated with a transaction. You must complete any transactions associated with that thread before beginning a new transaction. A SystemException is thrown if the transaction manager (i.e., the EJB server) encounters an unexpected error condition.

commit()

The commit() method completes the transaction that is associated with the current thread. When commit() is executed, the current thread is no longer associated with a transaction. This method can throw several checked exceptions. An IllegalStateException is thrown if the current thread is not associated with a transaction. A SystemException is thrown if the transaction manager (the EJB server) encounters an unexpected error condition. A TransactionRolledbackException is thrown when the entire transaction is rolled back instead of committed; this can happen if one of the resources was unable to perform an update or if the UserTransaction.rollBackOnly() method was called. A HeuristicRollbackException indicates that heuristic decisions were made by one or more resources to roll back the transaction. A HeuristicMixedException indicates that heuristic decisions were made by resources to both roll back and commit the transaction; that is, some resources decided to roll back while others decided to commit.

rollback()

The rollback() method is invoked to roll back the transaction and undo updates. The rollback() method can throw one of three different checked exceptions. A SecurityException is thrown if the thread using the UserTransaction object is not allowed to roll back the transaction. An IllegalStateException is thrown if the current thread is not associated with a transaction. A SystemException is thrown if the transaction manager (the EJB server) encounters an unexpected error condition.

setRollbackOnly()

The setRollbackOnly() method is invoked to mark the transaction for rollback. This means that, whether or not the updates executed within the transaction succeed, the transaction must be rolled back when completed. This method can be invoked by any TX_BEAN_MANAGED EJB participating in the transaction or by the client application. The setRollBackOnly() method can throw one of two checked exceptions. An IllegalStateException is thrown if the current thread is not associated with a transaction. A SystemException is thrown if the transaction manager (the EJB server) encounters an unexpected error condition.

setTransactionTimeout(int seconds)

The setTransactionTimeout(int seconds) method sets the life span of a transaction; i.e., how long it will live before timing out. The transaction must complete before the transaction timeout is reached. If this method is not called, the transaction manager (EJB server) automatically sets the timeout. If this method is invoked with a value of 0 seconds, the default timeout of the transaction manager will be used. This method must be invoked after the begin() method. A SystemException is thrown if the transaction manager (EJB server) encounters an unexpected error condition.

getStatus()

The getStatus() method returns an integer that can be compared to constants defined in the javax.transaction.Status interface. This method can be used by a sophisticated programmer to determine the status of a transaction associated with a UserTransaction object. A SystemException is thrown if the transaction manager (EJB server) encounters an unexpected error condition.

14.5.4 Status

Status is a simple interface that contains no methods, only constants. Its sole purpose is to provide a set of constants that describe the current status of a transactional object—in this case, the UserTransaction:

interface javax.transaction.Status
{
    public final static int STATUS_ACTIVE;
    public final static int STATUS_COMMITTED;
    public final static int STATUS_COMMITTING;
    public final static int STATUS_MARKED_ROLLBACK;
    public final static int STATUS_NO_TRANSACTION;
    public final static int STATUS_PREPARED;
    public final static int STATUS_PREPARING;
    public final static int STATUS_ROLLEDBACK;
    public final static int STATUS_ROLLING_BACK;
    public final static int STATUS_UNKNOWN;
}

The value returned by getStatus() tells the client using the UserTransaction the status of a transaction. Here's what the constants mean:

STATUS_ACTIVE

An active transaction is associated with the UserTransaction object. This status is returned after a transaction has been started and prior to a transaction manager beginning a two-phase commit. (Transactions that have been suspended are still considered active.)

STATUS_COMMITTED

A transaction is associated with the UserTransaction object; the transaction has been committed. It is likely that heuristic decisions have been made; otherwise, the transaction would have been destroyed and the STATUS_NO_TRANSACTION constant would have been returned instead.

STATUS_COMMITTING

A transaction is associated with the UserTransaction object; the transaction is in the process of committing. The UserTransaction object returns this status if the transaction manager has decided to commit but has not yet completed the process.

STATUS_MARKED_ROLLBACK

A transaction is associated with the UserTransaction object; the transaction has been marked for rollback, perhaps as a result of a UserTransaction.setRollbackOnly() operation invoked somewhere else in the application.

STATUS_NO_TRANSACTION

No transaction is currently associated with the UserTransaction object. This occurs after a transaction has completed or if no transaction has been created. This value is returned rather than throwing an IllegalStateException.

STATUS_PREPARED

A transaction is associated with the UserTransaction object. The transaction has been prepared, which means that the first phase of the two-phase commit process has completed.

STATUS_PREPARING

A transaction is associated with the UserTransaction object; the transaction is in the process of preparing, which means that the transaction manager is in the middle of executing the first phase of the two-phase commit.

STATUS_ROLLEDBACK

A transaction is associated with the UserTransaction object; the outcome of the transaction has been identified as a rollback. It is likely that heuristic decisions have been made; otherwise, the transaction would have been destroyed and the STATUS_NO_TRANSACTION constant would have been returned.

STATUS_ROLLING_BACK

A transaction is associated with the UserTransaction object; the transaction is in the process of rolling back.

STATUS_UNKNOWN

A transaction is associated with the UserTransaction object; its current status cannot be determined. This is a transient condition and subsequent invocations will ultimately return a different status.

14.5.5 EJBContext Rollback Methods

Only BMT beans have access to the UserTransaction from the EJBContext and JNDI ENC. Container-managed transaction (CMT) beans cannot use the UserTransaction. CMT beans use the setRollbackOnly() and getRollbackOnly() methods of the EJBContext to interact with the current transaction instead.

The setRollbackOnly() method gives an enterprise bean the power to veto a transaction. This power can be used if the enterprise bean detects a condition that would cause inconsistent data to be committed when the transaction completes. Once an enterprise bean invokes the setRollbackOnly() method, the current transaction is marked for rollback and cannot be committed by any other participant in the transaction—including the container.

The getRollbackOnly() method returns true if the current transaction has been marked for rollback. This can be used to avoid executing work that would not be committed anyway. If, for example, an exception is thrown and captured within an enterprise bean method, getRollbackOnly() can be used to determine whether the exception caused the current transaction to be rolled back. If it did, there is no sense in continuing the processing. If it did not, the EJB has an opportunity to correct the problem and retry the task that failed. Only expert EJB developers should attempt to retry tasks within a transaction. Alternatively, if the exception did not cause a rollback (getRollbackOnly() returns false), a rollback can be forced using the setRollbackOnly() method.

BMT beans must not use the setRollbackOnly() and getRollbackOnly() methods of the EJBContext. BMT beans should use the getStatus() and rollback() methods on the UserTransaction object to check for rollback and force a rollback, respectively.

14.6 Exceptions and Transactions

Exceptions have a large impact on the outcome of transactions and must be discussed in detail so that bean developers understand the relationship between them.

14.6.1 Application Exceptions Versus System Exceptions

An application exception is any exception that does not extend java.lang.RuntimeException or java.rmi.RemoteException. System exceptions are java.lang.RuntimeException and its subtypes, including EJBException.

An application exception must never extend either the RuntimeException, the RemoteException, or one of their subtypes.

Transactions are automatically rolled back if a system exception is thrown from an enterprise bean method. Transactions are not automatically rolled back if an application exception is thrown. If you remember these two rules, you will be well prepared to deal with exceptions and transactions in EJB.

The bookPassage() method is a good illustration of an application exception and how it is used. The following sections show the code for the bookPassage() method.

14.6.1.1 EJB 2.0: bookPassage( ) method
public TicketDO bookPassage(CreditCardDO card, double price)
    throws IncompleteConversationalState {
                   
    if (customer == null || cruise == null || cabin == null) {
        throw new IncompleteConversationalState();
    }
    try {
        ReservationHomeLocal resHome = (ReservationHomeLocal)
            jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal");
            
        ReservationLocal reservation =
            resHome.create(customer, cruise, cabin, price);
                
        Object ref = 
            jndiContext.lookup("java:comp/env/ejb/ProcessPaymentHomeRemote");

        ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote)
            PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class);
            
        ProcessPaymentRemote process = ppHome.create();
        process.byCredit(customer, card, price);

        TicketDO ticket = new TicketDO(customer,cruise,cabin,price);

        return ticket;
    } catch(Exception e) {
        throw new EJBException(e);
    }
}
14.6.1.2 EJB 1.1: bookPassage( ) method
public TicketDO bookPassage(CreditCardDO card, double price)
    throws IncompleteConversationalState {
                   
    if (customer == null || cruise == null || cabin == null) {
        throw new IncompleteConversationalState();
    }
    try {
        ReservationHomeRemote resHome = (ReservationHomeRemote) 
            getHome("ReservationHomeRemote", ReservationHomeRemote.class);
        ReservationRemote reservation =
            resHome.create(customer, cruise, cabin, price);
        ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote) 
            getHome("ProcessPaymentHomeRemote", ProcessPaymentHomeRemote.class);
        ProcessPaymentRemote process = ppHome.create();
        process.byCredit(customer, card, price);

        TicketDO ticket = new TicketDO(customer,cruise,cabin,price);

        return ticket;
    } catch(Exception e) {
        throw new EJBException(e);
    }
}
14.6.1.3 System exceptions

System exceptions are the RuntimeException and its subclasses. The EJBException is a subclass of the RuntimeException, so it is considered a system exception.

System exceptions always cause a transaction to roll back when they are thrown from an enterprise bean method. Any RuntimeException (EJBException, NullPointerException, IndexOutOfBoundsException, etc.) thrown within the bookPassage() method is handled by the container automatically and results in a transaction rollback. In Java, RuntimeException types do not need to be declared in the throws clause of the method signature or handled using try/catch blocks; they are automatically thrown from the method.

RuntimeException types thrown from within enterprise beans always cause the current transaction to roll back. If the method in which the exception occurs started the transaction, the transaction is rolled back. If the transaction started from a client that invoked the method, the client's transaction is marked for rollback and cannot be committed.

System exceptions are handled automatically by the container, which will always:

  • Roll back the transaction.

  • Log the exception to alert the system administrator.

  • Discard the EJB instance.

RuntimeExceptions thrown from the callback methods (ejbLoad(), ejbActivate(), etc.) are treated the same as exceptions thrown from business methods.

While EJB requires that system exceptions be logged, it does not specify how exceptions should be logged or the format of the log file. The exact mechanism for recording exceptions and reporting them to the system administrator is left to the vendor.

When a system exception occurs, the EJB instance is discarded, which means that it is dereferenced and garbage collected. The container assumes that the EJB instance may have corrupt variables or otherwise be unstable and is therefore unsafe to use.

The impact of discarding an EJB instance depends on the enterprise bean's type. In the case of stateless session beans and entity beans, the client does not notice that the instance was discarded. These instance types are not dedicated to a particular client; they are swapped in and out of an instance pool, so any instance can service a new request. With stateful session beans, however, the impact on the client is severe. Stateful session beans are dedicated to a single client and maintain conversational state. Discarding a stateful bean instance destroys the instance's conversational state and invalidates the client's reference to the EJB. When stateful session instances are discarded, subsequent invocations of the EJB's methods by the client result in a NoSuchObjectException, a subclass of the RemoteException.[5]

With message-driven beans, a system exception thrown by the onMessage() method or one of the callback methods (ejbCreate() or ejbRemove()) will cause the bean instance to be discarded. If the MDB was BMT bean, the message it was handling may or may not be redelivered, depending on when the EJB container acknowledges delivery. In the case of container-managed transactions, the container will roll back the transaction, so the message will not be acknowledged and may be redelivered.

In session and entity beans, when a system exception occurs and the instance is discarded, a RemoteException is always thrown to remote clients; that is, clients using the beans' remote component interfaces. If the client started the transaction, which was then propagated to the EJB, a system exception (thrown by the enterprise bean method) will be caught by the container and rethrown as a javax.transaction.TransactionRolledbackException. The TransactionRolledbackException is a subtype of the RemoteException; it is a more explicit indication to the client that a rollback occurred.

In EJB 2.0 session and entity beans, when a system exception occurs and the instance is discarded, an EJBException is always thrown to any local enterprise bean clients (i.e., clients using the enterprise bean's local component interfaces). If the client started the transaction and it was then propagated to the EJB, a system exception (thrown by the enterprise bean method) will be caught by the container and rethrown as a javax.ejb.TransactionRolledbackLocalException. The TransactionRolledbackLocalException is a subtype of the EJBException; it is a more explicit indication to the client that a rollback occurred. In all other cases, whether the EJB is container-managed or bean-managed, a RuntimeException thrown from within the enterprise bean method will be caught by the container and rethrown as an EJBException.

An EJBException should generally be thrown when a subsystem throws an exception, such as JDBC throwing a SQLException or JMS throwing a JMSException. In some cases, however, the bean developer may attempt to handle the exception and retry an operation rather then throw an EJBException. This should be done only when the exceptions thrown by the subsystem and their repercussions on the transaction are well understood. As a rule of thumb, rethrow subsystem exceptions as EJBExceptions and allow the EJB container to roll back the transaction and discard the bean instance.

The callback methods defined in the javax.ejb.EntityBean and javax.ejb.SessionBean interfaces declare the java.rmi.RemoteException in their throws clauses. This is left over from EJB 1.0 and has been deprecated since EJB 1.1. You should never throw RemoteExceptions from callback methods or any other bean class methods.

14.6.1.4 Application exceptions

An application exception is normally thrown in response to a business-logic error, as opposed to a system error. Application exceptions are always delivered directly to the client, without being repackaged as RemoteException or EJBException (EJB 2.0) types. They do not typically cause transactions to roll back; the client usually has an opportunity to recover after an application exception is thrown. For example, the bookPassage() method throws an application exception called IncompleteConversationalState; this is an application exception because it does not extend RuntimeException or RemoteException. The IncompleteConversationalState exception is thrown if one of the arguments passed into the bookPassage() method is null. (Application errors are frequently used to report validation errors like this.) In this case, the exception is thrown before tasks are started and is clearly not the result of a subsystem (e.g., JDBC, JMS, Java RMI, JNDI) failure.

Because it is an application exception, throwing an IncompleteConversationalState exception does not result in a transaction rollback. The exception is thrown before any work is done, avoiding unnecessary processing by the bookPassage() method and providing the client (the enterprise bean or application that invoked the bookPassage() method) with an opportunity to recover and possibly retry the method call with valid arguments.

Business methods defined in the remote and local interfaces can throw any kind of application exception. These application exceptions must be declared in the method signatures of the remote and local interfaces and in the corresponding methods in the enterprise EJB classes.

The EJB create, find, and remove methods can also throw several exceptions defined in the javax.ejb package: CreateException, DuplicateKeyException, FinderException, ObjectNotFoundException, and RemoveException. These exceptions are considered application exceptions: they are delivered to the client as-is, without being repackaged as RemoteExceptions. Furthermore, these exceptions don't necessarily cause a transaction to roll back, giving the client the opportunity to retry the operation. These exceptions may be thrown by the EJBs themselves; in the case of container-managed persistence, the container can also throw any of these exceptions while handling the EJB's create, find, or remove methods (ejbCreate(), ejbFind(), and ejbRemove()). The container might, for example, throw a CreateException if it encounters a bad argument while attempting to insert a record for a container-managed EJB. You can always choose to throw a standard application exception from the appropriate method regardless of how persistence is managed.

Here is a detailed explanation of the five standard application exceptions and the situations in which they are thrown:

CreateException

The CreateException is thrown by the create() method in the remote interface. This exception can be thrown by the container if the container is managing persistence, or it can be thrown explicitly by the EJB developer in the ejbCreate() or ejbPostCreate() methods. It indicates that an application error (invalid arguments, etc.) occurred while the EJB was being created. If the container throws this exception, it may or may not roll back the transaction. Explicit transaction methods must be used to determine the outcome. Bean developers should roll back the transaction before throwing this exception only if data integrity is a concern.

DuplicateKeyException

The DuplicateKeyException is a subtype of the CreateException; it is thrown by the create() method in the remote interface. This exception can be thrown by the container if the container is managing persistence, or it can be thrown explicitly by the EJB developer in the ejbCreate() method. It indicates that an EJB with the same primary key already exists in the database. The transaction is typically not rolled back by the EJB provider or container before throwing this exception.

FinderException

The FinderException is thrown by the find methods in the home interface. This exception can be thrown by the container if the container is managing persistence, or it can be thrown explicitly by the EJB developer in the ejbFind() methods. It indicates that an application error (invalid arguments, etc.) occurred while the container was attempting to find the EJBs. Do not use this method to indicate that entities were not found. Multi-entity find methods return an empty collection if no entities were found; single-entity find methods throw an ObjectNotFoundException to indicate that no object was found. The transaction is typically not rolled back by the EJB provider or container before throwing this exception.

ObjectNotFoundException

The ObjectNotFoundException is thrown from a single-entity find method to indicate that the container could not find the requested entity. This exception can be thrown either by the container (if the container is managing persistence) or explicitly by the EJB developer in the ejbFind() methods. It should not be thrown to indicate a business-logic error (invalid arguments, etc.). Use the FinderException to indicate business-logic errors in single-entity find methods. The ObjectNotFoundException is thrown by single-entity find methods only to indicate that the entity requested was not found. Find methods that return multiple entities should return an empty collection if nothing is found. The transaction is typically not rolled back by the EJB provider or container before throwing this exception.

RemoveException

The RemoveException is thrown from the remove() methods in the remote and home interfaces. This exception can be thrown by the container if the container is managing persistence, or it can be thrown explicitly by the EJB developer in the ejbRemove() method. It indicates that an application error has occurred while the EJB was being removed. The transaction may or may not have been rolled back by the container before throwing this exception. Explicit transaction methods must be used to determine the outcome. Bean developers should roll back the transaction before throwing the exception only if data integrity is a concern.

Table 14-1 summarizes the interactions between different types of exceptions and transactions in session and entity beans.

Table 14-1. Exception summary for session and entity beans

Transaction scope

Transaction type attributes

Exception thrown

Container's action

Client's view

Client-initiated transaction.

The transaction is started by the client (application or EJB) and is propagated to the enterprise bean method.

transaction-type =

Container

transaction-attribute =

Required |

Mandatory |

Supports

Application exception

If the EJB invoked setRollbackOnly(), mark the client's transaction for rollback.

Rethrow the application exception.

Receives the application exception. The client's transaction may or may not have been marked for rollback.

   

System exception

Mark the client's transaction for rollback.

Log the error.

Discard the instance.

Rethrow the JTA TransactionRolledbackException to remote clients or the javax.ejb.TransactionRolledbackLocalException to EJB 2.0 local clients.

Remote clients receive the JTA TransactionRolledbackException; local clients receive the javax.ejb.TransactionRolledbackLocalException.

The client's transaction has been rolled back.

Container-initiatedtransaction.

The transaction started when the EJB's method was invoked and will end when the method completes.

transaction-type =

Container

transaction-attribute =

Required |

RequiresNew

Application exception

If the EJB invoked setRollbackOnly(), roll back the transaction and rethrow the application exception.

If the EJB did not explicitly roll back the transaction, attempt to commit the transaction and rethrow the application exception.

Receives the application exception. The EJB's transaction may or may not have been rolled back. The client's transaction is not affected.

   

System exception

Roll back the transaction.

Log the error.

Discard the instance.

Rethrow the RemoteException to remote clients or the EJBException to EJB 2.0 local clients.

Remote clients receive the RemoteException; local EJB 2.0 clients receive the EJBException.

The EJB's transaction was rolled back.

The client's transaction may or may not be marked for rollback, depending on the vendor.

The bean is not part of a transaction.

The EJB was invoked but does not propagate the client's transaction and does not start its own transaction.

transaction-type =

Container

transaction-attribute =

Never |

NotSupported |

Supports |

Application exception

Rethrow the application exception.

Receives the application exception.

The client's transaction is not affected.

   

System exception

Log the error.

Discard the instance.

Rethrow the RemoteException to remote clients or the EJBException to EJB 2.0 local clients.

Remote clients receive the RemoteException; local EJB 2.0 clients receive the EJBException.

The client's transaction may or may not be marked for rollback, depending on the vendor.

Bean-managed transaction.

The stateful or stateless session EJB uses the EJBContext to explicitly manage its own transaction.

transaction-type =

Bean

transaction-attribute =

Bean-managed transaction EJBs do not use transaction attributes.

Application exception

Rethrow the application exception.

Receives the application exception.

The client's transaction is not affected.

   

System exception

Roll back the transaction.

Log the error.

Discard the instance.

Rethrow the RemoteException to remote clients or the EJBException to EJB 2.0 local clients.

Remote clients receive the RemoteException; local EJB 2.0 clients receive the EJBException.

The client's transaction is not affected.

Table 14-2 summarizes the interactions between different types of exceptions and transactions in message-driven beans.

Table 14-2. Exception summary for message-driven beans

Transaction scope

Transaction type attributes

Exception thrown

Container's action

Container-initiated transaction.

The transaction started before the onMessage() method was invoked and will end when the method completes.

transaction-type =

Container

transaction-attribute =

Required

System exception

Roll back the transaction.

Log the error.

Discard the instance.

Container-initiated transaction.

No transaction was started.

transaction-type =

Container

transaction-attribute =

NotSupported

System exception

Log the error.

Discard the instance.

Bean-managed transaction.

The message-driven bean uses the EJBContext to explicitly manage its own transaction.

transaction-type =

Bean

transaction-attribute =

Bean-managed transaction EJBs do not use transaction attributes.

System exception

Roll back the transaction.

Log the error.

Discard the instance.

14.7 Transactional Stateful Session Beans

As you saw in Chapter 12, session beans can interact directly with the database as easily as they can manage the workflow of other enterprise beans. The ProcessPayment EJB, for example, makes inserts into the PAYMENT table when the byCredit() method is invoked, and the TravelAgent EJB queries the database directly when the listAvailableCabins() method is invoked. Stateless session beans such as the ProcessPayment EJB have no conversational state, so each method invocation must make changes to the database immediately. With stateful session beans, however, we may not want to make changes to the database until the transaction is complete. Remember, a stateful session bean can be just one participant out of many in a transaction, so it may be advisable to postpone database updates until the entire transaction is committed or to avoid updates if it is rolled back.

There are several different scenarios in which a stateful session bean would want to cache changes before applying them to the database. For example, think of a shopping cart implemented by a stateful session bean that accumulates several items for purchase. If the stateful bean implements SessionSynchronization, it can cache the items and write them to the database only when the transaction is complete.

The javax.ejb.SessionSynchronization interface allows a session bean to receive additional notification of the session's involvement in transactions. The addition of these transaction callback methods by the SessionSynchronization interface expands the EJB's awareness of its life cycle to include a new state, the Transactional Method-Ready state. This third state, although not discussed in Chapter 12, is always a part of the life cycle of a transactional stateful session bean. Implementing the SessionSynchronization interface simply makes it visible to the EJB. Figure 14-11 shows the stateful session bean with the additional state.

Figure 14-11. Life cycle of a stateful session bean

figs/ejb3_1411.gif

The SessionSynchronization interface is defined as follows:

package javax.ejb;

public interface javax.ejb.SessionSynchronization {
    public abstract void afterBegin() throws RemoteException;
    public abstract void beforeCompletion() throws RemoteException;
    public abstract void afterCompletion(boolean committed) throws RemoteException;
}

When a method of the SessionSynchronization bean is invoked outside of a transaction scope, the method executes in the Method-Ready state, as discussed in Chapter 12. However, when a method is invoked within a transaction scope (or creates a new transaction), the EJB moves into the Transactional Method-Ready state.

14.7.1 The Transactional Method-Ready State

The SessionSynchronization methods are called in the Transactional Method-Ready state.

14.7.1.1 Transitioning into the Transactional Method-Ready state

When a transactional method is invoked on a SessionSynchronization bean, the stateful bean becomes part of the transaction. This causes the afterBegin() callback method defined in the SessionSynchronization interface to be invoked. This method should take care of reading any data from the database and storing the data in the bean's instance fields. The afterBegin() method is called before the EJB object delegates the business-method invocation to the EJB instance.

14.7.1.2 Life in the Transactional Method-Ready state

When the afterBegin() callback method completes, the business method originally invoked by the client is executed on the EJB instance. Any subsequent business methods invoked within the same transaction will be delegated directly to the EJB instance.

Once a stateful session bean is a part of a transaction—whether it implements SessionSynchronization or not—it cannot be accessed by any other transactional context. This is true regardless of whether the client tries to access the EJB with a different context or the EJB's own method creates a new context. If, for example, a method with a transaction attribute of RequiresNew is invoked, the new transactional context causes an error to be thrown. Since the NotSupported and Never attributes specify a different transactional context (no context), invoking a method with these attributes also causes an error. A stateful session bean cannot be removed while it is involved in a transaction. This means that invoking ejbRemove() while the SessionSynchronization bean is in the middle of a transaction will cause an error to be thrown.

At some point, the transaction in which the SessionSynchronization bean has been enrolled will come to an end. If the transaction is committed, the SessionSynchronization bean will be notified through its beforeCompletion() method. At this time, the EJB should write its cached data to the database. If the transaction is rolled back, the beforeCompletion() method will not be invoked, avoiding the pointless effort of writing changes that won't be committed to the database.

The afterCompletion() method is always invoked, whether the transaction ended successfully with a commit or unsuccessfully with a rollback. If the transaction was a success—which means that beforeCompletion() was invoked—the committed parameter of the afterCompletion() method will be true. If the transaction was unsuccessful, committed will be false.

It may be desirable to reset the stateful session bean's instance variables to some initial state if the afterCompletion() method indicates that the transaction was rolled back.

[1]  Not all EJB servers support distributed transactions.

[2]  Isolation conditions are covered in detail by the ANSI SQL-92 Specification, Document Number: ANSI X3. 135-1992 (R1998).

[3]  Isolation conditions are covered in detail by ANSI SQL-92 Specification, Document Number: ANSI X3.135- 1992 (R1998).

[4]  Only beans declared as managing their own transactions (bean-managed transaction beans) can use the UserTransaction interface.

[5]  Although the instance is always discarded with a RuntimeException, the impact on the remote reference may vary depending on the vendor.

CONTENTS