Special Edition Using Enterprise JavaBeans 2.0
Using Bean-Managed Transactions
The first point to make about bean-managed transaction demarcation is that you should avoid using it. When you choose this method of transaction management for a session or message-driven bean (you're not allowed to use it for entity beans), you're responsible for starting each transaction that applies to the bean's methods and then committing it or rolling it back to end it. You're offered this option to handle situations where you need to control transaction boundaries in a way that you can't achieve using container-managed demarcation. You should consider this approach to be only for advanced programmers with detailed knowledge of the application and managing distributed transactions. To use bean-managed transaction demarcation within an enterprise bean you obtain a UserTransaction object by calling the getUserTransaction method of EJBContext . A UserTransaction allows you to programmatically define transaction boundaries. Because the demarcation instructions are part of your code and not done declaratively , a bean that uses this approach is harder to reuse across applications. An application assembler or deployer can't change the transactional behavior of a bean when transaction demarcation has been programmed into its methods. It's an error for you to include trans-attribute entries in the deployment descriptor for a bean using bean-managed demarcation for this reason. Also remember that your choice of container- or bean-managed demarcation applies to a bean as a whole and not to individual methods. The UserTransaction interface itself isn't complicated. The hard part about managing your own transactions is having the foresight to handle the different ways your methods might be used and the error conditions they might encounter. The methods of UserTransaction are straightforward on their own. To employ UserTransaction within a session or message-driven bean, you implement code somewhat like the following: public void myTransactionalMethod() { try { // obtain access to a UserTransaction UserTransaction tx = myEJBContext.getUserTransaction(); // start a JTA transaction tx.begin(); // call other objects and resources to perform work under the transaction ... // complete the transaction tx.commit(); } catch (Exception e) { // report any error as a system exception // (this is covered in Chapter 13) throw new javax.ejb.EJBException(e); } } The UserTransaction Interface
As you saw in the preceding example, a session or message-driven bean can obtain a UserTransaction from its EJBContext . Calling this method from an entity bean or any other bean using container-managed demarcation results in an IllegalStateException . Non-EJB clients can obtain a UserTransaction using a JNDI lookup; you'll see how this is done later in the "Using Client-Demarcated Transactions" section. After you obtain a UserTransaction , you use the methods of this interface to manage the transaction boundaries of a method. The following descriptions explain how you use each method of the interface. begin
After you obtain a UserTransaction , you call its begin method to actually start a transaction. Nested transactions aren't supported by EJB, so you'll get a NotSupportedException if you call begin when there's already a transaction associated with the current thread. A SystemException is thrown if a transaction can't be started because of some other error. Once a transaction is started, it becomes the transaction context associated with the bean instance that created it. This context is automatically propagated to any components or resources that the instance accesses during the scope of the transaction. setTransactionTimeout
A transaction isn't allowed to continue indefinitely. Transaction managers impose a time limit after which a transaction is forced to roll back if it hasn't completed. Each transaction manager has a default time limit that it imposes, but you can override this if necessary. After calling begin , you can call setTransactionTimeout , which accepts a single int argument expressed in seconds, to establish the time limit for a transaction. You can reset a transaction's timeout back to the transaction manager's default by calling this method and passing a zero. The default timeout is adequate for the majority of transactions executed by an application, but should you need to perform an update that requires a significant amount of time, this is your way to make the transaction manager aware of that. commit
You complete the transaction associated with the current thread and commit its results by calling the commit method. When commit has completed, the current thread no longer has an assigned transaction context. This method can fail for several reasons. You'll receive a RollbackException if the transaction rolls back when the commit is attempted. This exception indicates that either one of the resource managers couldn't commit the transaction's updates or the transaction had been marked for rollback by an earlier call to setRollbackOnly on the UserTransaction . If a heuristic decision causes some or all of the transaction to roll back, either a HeuristicRollbackException (everything rolled back) or a HeuristicMixedException (some changes rolled back, but some committed) will be thrown. If you call commit and there isn't a transaction associated with the current thread, IllegalStateException is thrown. A SecurityException indicates a permission problem and SystemException is used to report any other unexpected error. rollback
You can call rollback to complete a transaction without saving any of its updates. As with commit , this method will throw an IllegalStateException if the current thread isn't associated with a transaction or a SecurityException or SystemException if a permissions problem or unexpected error occurs during the rollback. setRollbackOnly
If you begin and complete a transaction within a single bean method, rolling back that transaction based on a decision made by that method is simple; you just call rollback instead of commit before returning from the method. However, multiple method calls and bean instances can be involved in a transaction and, as you'll see later in this section, bean-managed demarcation can be used with transactions that span method calls to a stateful session bean. If a bean method participating in a bean-managed transaction determines that the transaction should never be allowed to commit, the setRollbackOnly method must be called on the UserTransaction . If this is done, the transaction is guaranteed to never commit. This method applies most often when an error condition occurs that can't be handled in a way that guarantees the integrity of the transaction. Calling setRollbackOnly will result in an IllegalStateException if there is no associated transaction or a SystemException if an unexpected error occurs. You saw earlier in this chapter that the EJBContext interface also defines a setRollbackOnly method. You can only use a bean's EJBContext to mark a transaction for rollback when you're using container-managed demarcation (you'll get an IllegalStateException otherwise ). You're restricted to the methods of UserTransaction when you're controlling a transaction using bean-managed demarcation. getStatus
You can call getStatus to obtain information about the transaction associated with the current thread. This method will throw a SystemException to report unexpected errors, but it won't throw an exception if there isn't a transaction assigned. This method is the one way you can determine if an associated transaction exists. You need to compare the int result returned by getStatus to the constants defined by the Status interface to interpret its meaning. To learn if a bean-managed transaction has been marked for rollback, you must use getStatus instead of the getRollbackOnly method of EJBContext or an IllegalStateException will be thrown. Managing Your Transactions
A key difference between bean-managed and container-managed demarcation is that a client's transaction context is never applied to a bean that is using bean-managed transactions. If a client transaction exists when a bean method is called, the container suspends that transaction and resumes it after the method call has completed. Otherwise, the client would be responsible for demarcating the transaction applied to your bean method. Even though bean-managed demarcation makes you responsible for starting and completing a transaction, the container still handles the enlistment of resources in a transaction. Just as with container-managed demarcation, there are some restrictions on how you interact with these resources though. In particular, you have to control all transaction demarcation through the UserTransaction API and not using any API provided by a resource manager. For example, if you're accessing a database or a JMS session, you can't call commit or rollback on a java.sql.Connection or a javax.jms.Session . Such an attempt would interfere with the coordination provided by the transaction manager. A potential benefit of managing your own transactions relates to the number of transactions you can execute in response to a client call. Even though you can't execute nested transactions, you can execute more than one transaction during a method call when you're using bean-managed demarcation. As long as you commit or roll back each transaction before starting a new one, the container doesn't restrict how many you perform. Bean-Managed Demarcation for Stateful Session Beans
Unlike stateless session beans and message-driven beans, a stateful session bean isn't required to commit a transaction before returning from a business method. If a stateful session bean using bean-managed demarcation starts a transaction, that transaction can span multiple method calls before eventually being committed or rolled back. When this is done, the container suspends the transaction when each method call completes and resumes it when another method call is made. Just as a client transaction is never associated with a bean using bean-managed transactions, the bean's transaction is never associated with its client. The risk in this approach is that you must rely on the client to call a specific method to eventually commit the transaction. |