Special Edition Using Enterprise JavaBeans 2.0
Using Container-Managed Transactions
The recommended way to manage transactions in an EJB application is to let the container manage them for you. This is known as container-managed transaction demarcation and it allows you to direct the control of transactions using a declarative syntax. Instead of making calls to a UserTransaction object programmatically, you can determine how the boundaries of transactions should be defined through entries in the deployment descriptor for an EJB. This approach instructs the container to make calls to the transaction manager (through the TransactionManager interface) to start and stop transactions on your behalf . The first thing to understand about container-managed transaction demarcation is why it's useful. One of the reasons should be easy for you to accept after seeing the background for what's involved in managing distributed transactions. Transaction management is difficult and it requires complicated code that cannot fail without risking a loss of data integrity. Having the container do the work for you removes that risk from you and simplifies the code needed for your beans to be able to operate in transactions. A second benefit is that defining transactional behavior during deployment instead of within your code allows the behavior of your beans to be modified based on the application into which they're being assembled . Customization by an application assembler of a bean that uses container-managed transactions is possible without requiring any code changes. You choose an approach for managing the transactions associated with a bean using the transaction-type entry in the deployment descriptor: <ejb-jar> <enterprise-beans> ... <session> <ejb-name>AuctionHouse</ejb-name> <home>com.que.ejb20.auction.controller.AuctionHouseHome</home> <remote>com.que.ejb20.auction.controller.AuctionHouse</remote> <ejb-class>com.que.ejb20.auction.controller.AuctionHouseBean</ejb-class> <session-type>Stateless</session-type> <transaction-type>Container</transaction-type> ... </session> ... </enterprise-beans> </ejb-jar> The transaction-type tag, which can be defined to be either Container or Bean , is only supported for session and message-driven beans because entity beans are required to use container-managed transactions. Also important is the fact that this choice applies to an entire bean and not to individual methods. You cannot implement the transaction demarcation yourself for some methods in a bean and let the container do it for the others. Assigning Transaction Attributes
When you're using container-managed transactions for an enterprise bean, you don't access UserTransaction to start and end transactions within the bean's methods because the container takes care of the demarcation communication with the transaction manager. The way you tell the container what you want it to do is by including transaction attributes in the ejb-jar file's XML deployment descriptor. These attributes let you tell the container which methods need to be executed within transactions and how those transactions are to be managed. The container is able to fill this role because it intercepts every client call to a session or entity bean and every call to a message-driven bean's onMessage method. This provides the opportunity for the container to perform any necessary transaction management immediately before a bean method is invoked and immediately after it completes. To deploy an entity bean, you have to specify a transaction attribute for the following methods:
To deploy a session bean using container-managed transaction, you have to specify a transaction attribute for the methods defined in its component interface and its superinterfaces other than the methods defined by EJBObject and EJBLocalObject . An attribute must be specified for a message-driven bean's onMessage method for it to use container-managed demarcation. The following XML fragment shows an example of how this attribute ( trans-attribute ) can be specified: <ejb-jar> <enterprise-beans> ... <assembly-descriptor> <container-transaction> <description> Assign Required to all AuctionHouse methods </description> <method> <ejb-name>AuctionHouse</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> ... </assembly-descriptor> ... </enterprise-beans> </ejb-jar> In this example, the transaction attribute for every method of the AuctionHouse session bean is set to Required . You'll see what the allowed attribute values are and their meanings shortly, but for now, it's only important to see how they're assigned. You assign a transaction attribute using a container-transaction element in the deployment descriptor. This element can include a description and it must include one or more method elements and a single trans-attribute element. The method elements identify the method or methods the attribute applies to using one of the following three styles: <!-- Style 1 --> <method> <ejb-name>EJBNAME</ejb-name> <method-name>*</method-name> </method> <!-- Style 2 --> <method> <ejb-name>EJBNAME</ejb-name> <method-name>METHOD</method-name> </method>> <!-- Style 3 --> <method> <ejb-name>EJBNAME</ejb-name> <method-name>METHOD</method-name> <method-params> <method-param>PARAM-1</method-param> <method-param>PARAM-2</method-param> ... <method-param>PARAM-n</method-param> </method-params> <method> The three styles are the same in that they each require you to specify the ejb-name of the bean being referenced, but they differ in how the methods are identified. This is important because you're not required to assign the same transaction attribute to every method in a bean. If you want to use the same attribute for an entire bean, the first style allows you to do this by specifying an asterisk for the method name. This is a common scenario. The second style allows you to assign an attribute to a method with a specific name. If more than one overloaded method exists with this name, the attribute is applied to each of them. The third style allows you to refer to a single method that has an overloaded name by specifying its parameter list. Each method-param entry should be a fully qualified type name such as java.lang.String . To identify a method without any parameters, you just include an empty method-params element. If a method with the same name and signature is declared multiple times across a bean's home and component interfaces, you can include the optional method-intf element before the method-name to distinguish them if necessary. This element can be assigned a value of Home , Remote , LocalHome , or Local . You can use method-intf with any of the three styles of identifying methods. For example, the following entry would apply to all methods declared by a particular bean's remote interface: <method> <ejb-name>AuctionHouse</ejb-name> <method-intf>Remote</method-intf> <method-name>*</method-name> </method> It's possible to assign an attribute to all methods using the asterisk form and then override it for a subset of the methods. For example, the following deployment entries would assign Required to all methods other than getNonPendingAuctions : <ejb-jar> <enterprise-beans> ... <assembly-descriptor> <container-transaction> <description> Assign Required to all AuctionHouse methods </description> <method> <ejb-name>AuctionHouse</ejb-name> <method-name>*</method-name> </method> <trans-attribute>Required</trans-attribute> </container-transaction> <container-transaction> <description> Override the assignment for one method </description> <method> <ejb-name>AuctionHouse</ejb-name> <method-name>getNonPendingAuctions</method-name> </method> <trans-attribute>RequiresNew</trans-attribute> </container-transaction> ... </assembly-descriptor> ... </enterprise-beans> </ejb-jar> The assignment of transaction attributes is the responsibility of the bean provider, the application assembler, or the deployer. The bean provider or application assembler should either specify an attribute for every method that requires one or not specify any of the attributes for a bean and leave it to the deployer to do it. The following sections define the six supported transaction attributes and describe how they're used. Required
You will use the Required attribute more often than any other because you will typically be coding functionality into your beans that is used to do transactional work. This attribute tells the container that a method must be invoked within a valid transaction context. If the caller is executing under a transaction, that transaction will be applied to the method. If the caller doesn't supply a transaction context, the container starts a new transaction immediately before calling the method. This latter case always applies for message-driven beans that run under a transaction because they don't have a client to supply one. The Required attribute makes it easy for you to combine the work done by multiple methods into a single transaction. For example, a transaction can be started with a call to a session bean (either by the container or by the session bean itself) and then used to group the work of any number of entity bean methods deployed with a transaction attribute of Required . Each such method is executed under the existing transaction without any additional transactions being started. RequiresNew
The RequiresNew attribute is useful when a method needs to commit its results regardless of what takes place with any transaction that might be executing when the method is called. When this attribute is applied, the container always starts a new transaction before invoking the method and commits it before returning the result of the call to the client. If the client has an existing transaction context when the call is made, the container suspends that transaction and then resumes it after the method call has completed. An example of this attribute might be the need to connect to a JMS session and send a message that should be delivered regardless of what happens in any other transaction. NotSupported
Not all resource managers can turn their transaction management over to a J2EE application server. This isn't the case for a resource manager associated with a relational database, but it might be true for some other backend system such as an ERP application or an object-oriented database. If one of your bean methods accesses such a system, you should assign the NotSupported transaction attribute to that method. This makes it clear that any work done using this method cannot be automatically rolled back along with the other operations that take place during the transaction should a failure occur. When a method deployed with the NotSupported attribute is called, the container will suspend any client transaction that might be in progress and resume it after the method has completed. The transaction context isn't passed to any resource managers or other bean methods that are accessed during the call, so any work that is performed is done outside the scope of any global JTA transaction. When a resource can't be associated with a JTA transaction, it operates under its own local transaction. Remember that a local transaction is controlled by the resource manager instead of a coordinating transaction manager. The commit or rollback that completes a local transaction occurs outside the unit of work of any JTA transaction that might be in effect at the same time. Obviously this isn't desirable when multiple resource managers are being accessed because the operations are not all part of a single unit of work that can be managed atomically. A local transaction becomes a problem when a resource commits the work it has performed but the JTA transaction that's controlling the other resource managers does a rollback. Left alone, the system exists in an inconsistent state because the requirement for the operations that have been attempted to be atomic hasn't been satisfied. The only way to restore consistency at this point is to undo what has been committed by the local transaction. A typical approach for this is to employ a compensating transaction , which is a group of operations that, when executed, reverses the effects of a local transaction that has been committed. You only need to be concerned about compensating transactions when you access a resource that can't be included in a JTA transaction. Compensating transactions can work, but relying on them is risky. A server crash during a compensating transaction could prevent the undo of a commit from completing or you might encounter a situation where it isn't possible to undo a change made to a resource by a committed transaction. Besides risking a loss of atomic behavior, introducing committed changes into a system that must them be rolled back by a compensating transaction threatens consistency if the affected data is accessed by another client before the changes can be reversed . Supports
Supports is the one transaction attribute that causes the container to alter its behavior based on the transaction context passed by the client. If a valid transaction context is passed when a method deployed using Supports is called, the container implements the behavior for the Required attribute. If a call is made without a client transaction present, the container uses the behavior defined for NotSupported . This means that the method will be executed as part of a transaction only if the caller provides a transaction context. You should typically avoid using the Supports attribute because it leaves the transactional behavior of a bean method up to the client programmer. Before using Supports , you must be certain that a method works as desired both with and without being associated with a transaction. In general, this would only be true for a method that either makes no updates to resources or makes an update that is by nature atomic. An example of a naturally atomic update would be the execution of a single SQL statement through JBDC. In this case, applying a transaction that is created by the container before the method is called and ended immediately after the method completes doesn't change the method's behavior. The commit or rollback status of the SQL statement is determined by the database and the result is no different if a transaction applies to the method or not. If the method is instead called as part of a client transaction, the Supports attribute causes it to be included in the atomic unit of work being performed by the client. The method's update can then be committed or rolled back as part of a larger unit. The only advantage that Supports offers over Required is the performance savings of not requiring the container to create a transaction when the method is called without a client transaction present. Given that methods that perform single updates are the only ones that can be safely used this way, it's doubtful that a significant savings can be found by doing this.
Caution Be sure you're clear on the implications of the Supports attribute. Many developers new to EJB mistakenly view it as a good default choice because it intuitively sounds correct that a method should "support" transactions.
Mandatory
If a method is deployed with the Mandatory transaction attribute, the container will throw a javax.transaction.TransactionRequiredException (for a remote client) or a javax.ejb.TransactionRequiredLocalException (for a local client) if a client calls the method without a transaction context. This attribute makes it more difficult to use a bean across applications because of its rigid behavior. You should only use Mandatory if a method cannot operate correctly without a transaction or with a transaction started by the container. Never
Never is the opposite of Mandatory . If a method is deployed with the Never attribute, the container will throw a java.rmi.RemoteException if a remote client calls the method with a transaction context or a javax.ejb.EJBException if a local client does the same. Just as with Mandatory , Never restricts the composability of a bean. You should only use this attribute if you need to guarantee that a method call is not associated with a transaction. Don't Forget to Read the Fine Print
There are several restrictions and caveats associated with using container-managed transactions and assigning transaction attributes of which you need to be aware. Even though there are six values for a transaction attribute, not all of them are allowed in a particular context. Entity beans that are deployed using the EJB 2.0 version of CMP should only be assigned the Required , RequiresNew , and Mandatory attributes because their methods should always be accessed within the context of a transaction ( assuming the data store supports transactions). Message-driven beans only support the Required and NotSupported attributes because they are not called by a client. The container must create a transaction for a message-driven bean and either commit it or roll it back after onMessage completes, so RequiresNew and Supports are of no significance. Also, a message-driven bean has no client from which to receive a transaction context, so Mandatory and Never are not applicable either.
Note Container vendors have the option of allowing the NotSupported , Supports , and Never attributes for CMP entity beans. This would make sense only for a nontransactional data store (such as the file system). Using any of these attributes creates the possibility of data integrity problems, so it should be done with extreme caution. Also, because supporting these attributes with CMP is optional, any entity beans you develop this way will be nonportable.
In the discussion so far, the transaction context associated with a method call or a resource has referenced either a JTA transaction or null if no transaction is present. There is also the concept of an unspecified transaction context , which is a state with behavior left as an implementation decision for each J2EE vendor. The concept of an unspecified transaction context applies when NotSupported , Never , or Supports is the assigned transaction attribute and ejbCreate , ejbRemove , ejbPassivate , or ejbActivate is called for a session bean or ejbCreate or ejbRemove is called for a message-driven bean. When this happens, the container might use a null transaction context, treat each resource manager access as a transaction, treat multiple accesses of a resource manager as a single transaction, or some other behavior. You can't depend on any particular behavior in this situation, especially in the event of an error. This adds to the cautions against using Supports or Never . When a resource manager cannot be associated with a JTA transaction, you always need to assign NotSupported , but you do need to remember the potential problems of using such a resource. If you need to force a transaction to roll back when you're using container-managed demarcation, you must call the setRollbackOnly method of EJBContext . This won't cause an immediate rollback, but if an attempt to commit the transaction is later made by either the container or the client, the commit is guaranteed to fail. You'll see more about this method in Chapter 13, "Exception Handling." You can call getRollbackOnly to see if a transaction has already been marked for rollback. These method calls are only valid within bean methods deployed using the Required , RequiresNew , or Mandatory attributes. The container will throw a java.lang.IllegalStateException if either of them is called within a method deployed using Supports , NotSupported , or Never . The rollback functionality provided by EJBContext is the only transaction management operation that's available to you when you're using container-managed demarcation. A business method in a session or entity bean or an onMessage method using container-managed transactions must never attempt to obtain or use the UserTransaction interface. This interface can only be used by methods that implement bean-managed transactions. The container will throw an IllegalStateException if you call the EJBContext getUserTransaction method. Similarly, you're not allowed to interfere in the management of a transaction by calling any demarcation methods defined by a resource adapter. In particular, this forbids you from calling methods such as commit and rollback on a java.sql.Connection or javax.jms.Session . Transaction Synchronization
Most of the time, you'll perform database updates through entity beans that are being operated on by session bean methods executing within a transaction. However, allowing a session bean method to read directly from the database isn't unusual. You might also have a need to update the database or access some other transactional resource directly from a session bean. You wouldn't typically do this to manage data that's owned by an entity bean, but there could be other entries in a database that need to be managed this way. A transaction can't span multiple method calls to a stateless session bean because there is no conversational state maintained with the client. If you access the database from a stateless session bean method, any associated transaction has to start when the method is called and end when it completes. If you want to update the database, any processing that needs to happen to decide what to update has to be performed within a single method call to the bean and the results have to be written to the database before the method returns. The rules for transactions are different for stateless and stateful session beans. You'll see more about this later in the "Using Bean-Managed Transactions" section, but a transaction can span multiple method calls to a stateful session bean. This doesn't make any difference to you if you do all the processing associated with a task that updates the database within a single method call. Here, the data update takes place within the method and it's kept as long as the associated transaction commits. When a client is making multiple method calls as part of updating a particular set of data, you can use a spanning transaction to improve performance. With a stateful session bean, you can cache changes to the persistent data across several method calls and write them to the database only when the transaction is about to be committed. Otherwise, multiple update statements would be performed to massage the data into the final version that resulted from the client's requests . The only way for this caching approach to work is if the session bean is kept informed of a transaction's boundaries. Once a transaction starts, the bean instance can begin caching data, but it must perform its updates to the database before the transaction commits to keep the changes within the atomic unit of the transaction. Relying on the client to notify the bean instance of an imminent commit is too risky because data integrity could be lost if the client code failed to send the notification because of an error in the system or a simple programming bug. The only safe approach is to make it the responsibility of the container to do any notification that is needed. You saw it mentioned earlier in the chapter that OTS defines an optional synchronization interface that allows an object to be notified of a transaction's impending completion. The EJB specification requires that this interface be supported based on the javax.ejb.SessionSynchronization interface. Session bean instances that implement this interface are automatically registered with the javax.transaction.Transaction object that wraps the associated transaction context. SessionSynchronization , which is only for use with stateful session beans using container-managed transaction demarcation, is declared as follows : public interface SessionSynchronization { public void afterBegin() throws EJBException, java.rmi.RemoteException; public void beforeCompletion() throws EJBException, java.rmi.RemoteException; public void afterCompletion(boolean committed) throws EJBException, java.rmi.RemoteException; } As you'll see in the next section, this interface isn't needed if you're using bean-managed demarcation because you have full programmatic control of when a transaction commits. There's no notification necessary if you want to do any caching in that case. You can only deploy a bean that implements SessionSynchronization using the Required , RequiresNew , and Mandatory transaction attributes. Otherwise, the bean could be accessed without an associated transaction and the container wouldn't be able to send the required synchronization calls. afterBegin
If a stateful session bean implements SessionSynchronization , the container calls its afterBegin method immediately before invoking the first business method called by a client after the bean instance has been associated with a transaction. This would be the place for you to begin any data caching or perform any initial database access that might be required. beforeCompletion
The container calls a bean instance's beforeCompletion method before any resource managers enlisted in the transaction are instructed to begin a commit operation. If a rollback is about to occur, this method will not be called. If you have any cached data that needs to be written to the database, you must perform the updates during the call to this method. This is also your last chance to force the transaction to roll back by calling setRollbackOnly on the bean's EJBContext . This method allows you to throw a RemoteException , but that is only to maintain backward compatibility with EJB 1.1. If you need to report an error during the execution of beforeCompletion , you should throw an EJBException . As you'll see in Chapter 13, throwing this exception will cause the transaction to roll back. afterCompletion
The container calls afterCompletion once a transaction has either been committed or rolled back. You can check the method's boolean argument to learn if a commit occurred or not. If the transaction rolled back, you need to update any state maintained by the bean instance to match the state prior to the transaction. Be sure you understand the implications of this point. The state held by the instance variables in a session bean is not automatically reset when a transaction rolls back. To handle this for a stateful session bean, you have to implement SessionSynchronization and respond to rollbacks reported to afterCompletion by resetting the state yourself. There isn't a similar approach for stateless session beans. There, the correct approach is to not hold state that is affected by a transaction in the first place. As with beforeCompletion , you can throw an EJBException to report an error during afterCompletion but this won't affect the outcome of the transaction that has already ended. |