Item 31: Understand EJB transactional affinity



Item 31: Understand EJB transactional affinity

It's one of those aspects of the EJB Specification that many EJB programmers either never think about or, if they do, they sort of shunt it off to the side until later. It doesn't really have anything to do with programming, per se, since it never really shows up in code. Instead, most programmers just rely on the tools or IDE they're using to pick whatever values "feel right" and leave it at that. Unfortunately, the choice of transactional affinity on your EJB beans has as great an effect on the ultimate performance and scalability of your beans as anything else, perhaps more so.

Consider the classic Session Façade [Alur/Crupi/Malks, 341]—as an optimization to network trips, wrap all access to an entity bean via a session bean, thus forcing clients to access the entity bean in bulk rather than in individual method calls. So, for example, given an entity bean that has five attributes, rather than individually setting each attribute on the entity bean from the client, pass all five attributes via a single method call to the session bean. The session bean then does the set calls against the entity bean, thus cutting out a leg of the round-trips against the entity bean. Pretty straightforward, right?

Enter transactional affinity.

By this point, I assume that you're more or less familiar with the six different container-managed transactional affinity settings in EJB: Mandatory, Requires, RequiresNew, Supports, NotSupported, and Never. But what may not be familiar to you is the interaction of these affinity settings—how the container will react when a call comes from one bean into an other. For example, when a bean currently executing under a transaction calls another bean marked with Requires, the transaction from the first bean is propagated to the second, so both calls now operate under a single transaction. Figure details the complete set of interactions, both for container-managed and bean-managed transactions.

Transactional affinity setting effects
 

In transaction scope?

Root of transaction scope?

Shares caller's scope?

CMT: NotSupported

Never

Never

Never

CMT: Never

Never

Never

Never

CMT: Supports

If caller is

Never

If caller has one

CMT: Requires

Always

Only if caller has none

If caller has one

CMT: RequiresNew

Always

Always

Never

CMT: Mandatory

Always

Never

Always

BMT: User begins

Always

Always

Never

BMT: None started

Never

Never

Never


The key thing to recognize in this table is that transactional affinity interaction between beans will define a large part of how the bean will perform.

Assume, for the moment, that the entity bean's set methods, for whatever reason, are all marked with transactional affinity RequiresNew. This means that each set call must run under its own transaction, which in turn means that the entire state of the bean must be loaded at the start of the transaction and stored back to the database at the end of the call. Not only will this mandate an additional round-trip to the database for each call, but it also will go out with full two-phase distributed transaction semantics for it (unless your container supports the use of local transactions, in which case you should see Item 32).

In reality, rarely will an entity bean set transactional affinity on attribute methods to RequiresNew. It's far more likely that these methods will be set to Requires, indicating that it will borrow the transaction of its caller; this makes the bean most flexible for Session Façade usage. But if the Session Façade [Alur/Crupi/Malks, 341] session bean fronting it is set to Supports, NotSupported, or Never, we're back to the same situation described earlier—each call to a method on the entity bean will result in a new transaction against the database, meaning multiple round-trips.

Consider, for a moment, the average EJB container running without any special exclusive access to the database (which would not be portable across containers anyway; see Item 11 for details). Entity beans hold data inside the container also held in the database. Because these two processes frequently don't run on the same machine, much less inside the same process space, it's entirely possible that the EJB container holds data in memory that has been changed on the database.

Now picture that same entity bean being ordered to change an attribute value to some new value (whether from a Session Façade [Alur/Crupi/Malks, 341] or directly from a client, it really doesn't matter). Any entity bean access must be done under a container-managed transactional semantic (Section 17.6.1 in the EJB 2.1 Specification), so a transaction is started. However, because it's possible that the data in the database was modified through a process outside the EJB container's awareness, the EJB container must reload the bean entirely before allowing the call to proceed. The set can then take place, the transaction can commit, and the data is flushed back to the database.

What this implies, then, is that any access against the entity bean (again, without using vendor value-added exclusivity capabilities) means that the EJB container must make at least two round-trips from the EJB container to the database: one to fetch the data in, the other to flush the modified data out. Fortunately this only needs to be done at transactional boundaries, so an entity bean being modified through a properly marked transactionally aware Session Façade [Alur/Crupi/Malks, 341] won't do this except at transaction begin and transaction commit.

It's fair to point out, however, that this additional pair of round-trips is only necessary because the data is being held in two places simultaneously—in the relational database and in the EJB container. If entity beans are bypassed entirely in favor of direct SQL access against the relational database (see Item 41 for details), the data resides solely within the relational database and no additional fetch-flush cycles are needed.

It's not enough to recognize the impact of a given choice of transactional affinity for a given method. In addition, in order to maximize the use of a transaction (to avoid taking out more transactions than necessary) but minimize its lifetime (see Item 29), the union of transactional affinity on methods called within the EJB container also has to be considered because you're effectively calling out of your component, as described in Item 30. This implies, then, that for any given bean, call it bean X, defined within your EJB container, you must consider not only the transactional affinity of every method on X, but also the affinity of any bean calling X as well as the affinity of any bean called in turn by X. Failure to do so may result in longer transactions than absolutely necessary.