Bean Managed Transaction Demarcation

If an EJB is deployed using bean managed transaction demarcation (here referred to as BMTD, though this abbreviation isn't used in the EJB specification itself), the EJB container allows the bean to obtain a reference to a javax.transaction.UserTransaction object using the EJBContext. You can see this in Figure 8.3.

Motivation and Restrictions

An EJB might need to be deployed under BMTD if the conditions on which a transaction is started depend on some programmatic condition. It could be that one method starts the transaction and another method completes the transaction.

However, the cases where BMTD is needed are few and far between. Indeed, they are so rare that the EJB specification limits BMTD to Session beans. Entity beans can only be deployed with CMTD. This makes sense because Entity beans represent persistent transactional data; the transaction context should be as deterministic as possible.

Moreover, if a stateless Session bean starts a transaction, it must also commit that transaction before the method completes. After all, the stateless Session bean will have no memory of the client that just invoked its method after that method completes, so one cannot expect that the transaction context is somehow miraculously preserved. If you write a BMTD stateless Session bean that does not commit its own transaction, the EJB container will rollback the transaction and throw an exception back to the client (java.rmi.RemoteException for remote clients, or javax.ejb.EJBException for local).

Indeed, even with stateful Session beans, there is a restriction. Any current transaction in progress when the BMTD Session bean is called will be suspended by the EJB container, not propagated to the BMTD bean. It is possible that, from the bean's perspective, there is a current transaction, but that would refer to any transaction not committed when a method on that bean was last called.

Using the Java Transaction API

When a Session bean is deployed under BMTD, there is an implementation choice as to how it should manage its transactions. If interacting solely with an RDBMS, the Session bean can manage the transactions directly through the JDBC API. Alternatively, it can use the Java Transaction API, defined by the classes and interfaces in the javax.transaction and the javax.transaction.xa packages. The latter is to be preferred, if only because transactional access to Java Messaging Service resources (you'll be learning more about these tomorrow and the day after) can only be performed through the JTA API. Equally, servlets can also use the JTA API.

Note

The Java 2 Platform Enterprise Edition Specification, the document that defines the interoperability of all the technologies that make up the J2EE platform, only discusses transaction interoperability in the context of the JTA API.

If nothing else, the semantics of intermixing JDBC and JTA calls are not exhaustively defined, so this should be avoided to minimize chances of portability problems if moving to a different vendor's EJB container.


For a Session bean to start a transaction, it should first call the getUserTransaction() method of its SessionContext. You'll recall that this was the method that throws an exception under CMTD, but it is the centerpiece of transaction control under BMTD.

Obtaining a UserTransaction does not mean that a transaction has been started. Rather, it must be started using the begin() method. The transaction can then be completed using either the commit() or the rollback() method. The current status can also be obtained using getStatus(). This returns an int whose meaning is defined by the constants in the javax.transaction.Status interface. Some of the most common status values are shown in Table 8.2.

Table 8.2. Some of the Constants Defined in javax.transaction.Status
Constant Meaning Typical actions
STATUS_NO_TRANSACTION No transaction is active. tran.begin() to start new transaction.
STATUS_ACTIVE A transaction is active and can be used. Use resource manager. tran.commit() to commit tran.rollback() to rollback
STATUS_MARKED_ROLLBACK A transaction is active, but has been marked for rollback. Any attempt to commit it will result in a javax.transaction. RollbackException being thrown. tran.rollback()

Note

There are more constants in the Status interface than those listed in Table 8.2. Later today, (in the “Transactions: Behind the Scenes” section), you'll be learning about some of the “under-the-covers” mechanics of transaction management; the full list is presented there.


Listing 8.3 shows a possible implementation for the updateDetails() method of AdvertiseJob bean using BMTD.

Listing 8.3. BMTD Implementation of AdvertiseJobBean.updateDetails()
 1: package agency;
 2:
 3: import javax.ejb.*;
 4: import javax.transaction.*;
 5: // imports omitted
 6:
 7: public class AdvertiseJobBean extends SessionBean {
 8:     public void updateDetails (String description, String locationName, String[]
 skillNames) {
9:
10:         int initialTranStatus = beginTransactionIfRequired();
11:
12:         if (skillNames == null) {
13:             skillNames = new String[0];
14:         }
15:         List skillList;
16:         try {
17:             skillList = skillHome.lookup(Arrays.asList(skillNames));
18:         } catch(FinderException ex) {
19:             error("Invalid skill", ex, initialTranStatus); // throws an exception
20:             return;
21:         }
22:
23:         LocationLocal location=null;
24:         if (locationName != null) {
25:             try {
26:                 location = locationHome.findByPrimaryKey(locationName);
27:             } catch(FinderException ex) {
28:                 error("Invalid location", ex, initialTranStatus); // throws an exception
29:                 return;
30:             }
31:         }
32:
33:         job.setDescription(description);
34:         job.setLocation(location);
35:         job.setSkills(skillList);
36:
37:         completeTransactionIfRequired(initialTranStatus);
38:     }
39:
40:     private int beginTransactionIfRequired() {
41:
42:         UserTransaction tran = this.ctx.getUserTransaction();
43:         // start a new transaction if needed, else just use existing.
44:         // (simulates trans-attribute of REQUIRED)
45:         int initialTranStatus;
46:         try {
47:             initialTranStatus = tran.getStatus();
48:             switch(initialTranStatus) {
49:             case Status.STATUS_ACTIVE:
50:                 // just use
51:                 break;
52:             case Status.STATUS_NO_TRANSACTION:
53:                 // create
54:                 try {
55:                     tran.begin();
56:                 } catch(NotSupportedException ex) {
57:         // shouldn't happen (only thrown if asking for nested exception
58:         // and is not supported by the resource manager; not attempting
59:         // to do this here).
60:                     throw new EJBException( "Unable to begin transaction", ex);
61:                 }
62:                 break;
63:
64:             // code omitted; other Status' covered later
65:
66:             default:
67:                 throw new EJBException(
68:                 "Transaction status invalid, status = " + statusAsString
(initialTranStatus));
69:             }
70:         } catch(SystemException ex) {
71:             throw new EJBException("Unable to begin transaction", ex);
72:         }
73:
74:         return initialTranStatus;
75:     }
76:
77:     /**
78:     * expects initialTranStatus to be either STATUS_NO_TRANSACTION or STATUS_ACTIVE;
79:     * semantics undefined otherwise
80:     */
81:     private void completeTransactionIfRequired(int initialTranStatus) {
82:
83:         UserTransaction tran = this.ctx.getUserTransaction();
84:
85:         // if transaction was started, then commit / rollback as needed.
86:         // (simulates trans-attribute of REQUIRED)
87:         if (initialTranStatus == Status.STATUS_NO_TRANSACTION) {
88:             try {
89:                 if (tran.getStatus() == Status.STATUS_MARKED_ROLLBACK) {
90:                     tran.rollback();
91:                 } else {
92:                     tran.commit();
93:                 }
94:             } catch(Exception ex) {
95:                 throw new EJBException( "Unable to complete transaction", ex);
96:             }
97:         }
98:     }
99: }
						

The two helper methods, beginTransactionIfRequired() and completeTransactionIfRequired(), isolate the actual transaction management code, so it can be reused across different methods.

Deploying a BMTD Bean

Of course, when deploying a bean under BMTD, the deployment descriptor should indicate a transaction-type element of Bean, and you will not need any container-transaction elements under the application-assembly element. Figure 8.4 shows deploytool for the AdvertiseJob bean, indicating this fact.

Figure 8.4. BMTD is indicated through the deployment descriptor, as shown in deploytool.


Incidentally, if a BMTD Session bean calls getRollbackOnly() or setRollbackOnly() on its SessionContext, the EJB container will throw a java.lang.IllegalStateException. This is reasonable; if a BMTD has access to the UserTransaction object, it has no need for these methods. Instead, it can call the getStatus() method of UserTransaction, and explicitly call rollback() if needed.

Client-Demarcated Transactions

As well as Session beans managing their own transactions, it is also possible for clients to initiate the transaction and have it propagate through to the EJBs. Here, “client” means either an application client written using the Swing GUI (such as you have seen in the case study), or it could equally refer to a Web-based client implemented using servlets and JSPs.

For either of these clients, the EJB architecture requires that a UserTransaction context can be obtained via JNDI, bound under the name of java:comp/UserTransaction. So the code fragment shown in Listing 8.4 will do the trick.

Listing 8.4. Obtaining a UserTransaction Object from JNDI
 1: // assuming:
 2: // import javax.naming.*;
 3: // import javax.transaction.*;
 4: InitialContext ctx = new InitialContext();
 5: UserTransaction tran = (UserTransaction) ctx.lookup("java:comp/UserTransaction");
6: tran.begin();
 7: // call session and entity beans
 8: tran.commit();

That said, if you find yourself needing to use client-demarcated transactions, you should look at your application design and see if you are happy with it. After all, Session beans (are meant to) represent the application logic of your application, and this should surely include defining the transactional boundaries of changes to persistent data. Application clients should only provide a presentational interface to your application.

If that philosophical argument does not appeal, perhaps this might. A rogue client could be coded such that it begins a transaction, interacts with (and therefore ties up) various resources, such as Entity beans, and then not commit. This could seriously impact the performance of your application.

Exceptions Revisited

On Days 5 and 6, you learned the appropriate exceptions for your EJB to throw. In summary

  • To throw an application-level exception (indicating that a possibly recoverable condition has arisen), throw any checked exception (excluding java.rmi.RemoteException).

  • To throw a system-level exception (indicating that a non-recoverable severe condition has arisen), throw any java.lang.RuntimeException (usually a subclass of javax.ejb.EJBException).

If an application-level exception is thrown by a bean, it is up to that bean whether the current transaction is affected or not. If the bean takes no action other than raising its exception, the current transaction will be unaffected. The exception will simply propagate back to the calling client.

However, CMTD beans may decide to mark the current transaction for rollback, meaning that the “owner” of the transaction (the EJB container or some BMTD bean) will be unable to commit that transaction.

If a BMTD bean hits an error condition, it has a choice. Because it “owns” the transaction, it can simply do a rollback. Alternatively, it might elect to keep the transaction active.

If a system-level exception is thrown by a bean, this does have consequences for any current transaction. If any bean throws a system exception, the EJB container will mark the current transaction for rollback. If that bean happens to be a CMTD bean, and the EJB container started a transaction just before invoking the CMTD method (as a result of a Required or RequiresNew trans-attribute), the EJB container will actually rollback that transaction.

To summarize,

  • An application-level exception may or may not leave the current transaction active; use getStatus() or getRollbackOnly() to find out.

  • A system-level exception will either mark the current transaction for rollback or even do the rollback.

One last thing on transactions and exceptions. Most of the exceptions in javax.ejb (CreateException, RemoveException, and so on) are application exceptions. Some of these, especially with CMP Entity beans, are raised by the EJB container itself. Rather unhappily, the EJB specification does not mandate whether these application exceptions should mark any current transaction for rollback (see section 10.5.8). Instead, it just indicates that the getStatus() or getRollbackOnly() methods should be used to determine the status of the current transaction. In practical terms, what this means is that different EJB containers could have different implementations, compromising portability.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.71.115