How J2EE containers hide the complexity of transaction management
Brian Goetz (brian@quiotix.com), Principal Consultant, Quiotix Corp
commit() or rollback() methods, nor do they register what they've done with the transaction monitor. So how do the actions performed by J2EE components become part of the transaction?
Transparent resource enlistment
While the application state is manipulated by components, it is still stored in transactional resource managers (for example, databases and message queue servers), which can be registered as resource managers in a distributed transaction. In Part 1, we talked about how multiple resource managers can be enlisted in a single transaction, coordinated by a transaction manager. Resource managers know how to associate changes in application state with specific transactions.
But this just moves the focus of our question from the component to the resource manager -- how does the container figure out what resources are involved in the transaction so it can enlist them? Consider the following code, which might be found in a typical EJB session bean:
Listing 1. Transparent resource enlistment with bean-managed transactions
con1 and con2 appear to be ordinary JDBC connections such as those that would be returned fromDriverManager.getConnection() . We get these connections from a JDBC DataSource , which was obtained by looking up the name of the data source in JNDI. The name used in our EJB component to find the data source (java:comp/env/OrdersDB ) is specific to the component; the resource-ref section of the component's deployment descriptor maps it to the JNDI name of some application-wide DataSource managed by the container.
The hidden JDBC driver
Every J2EE container can create transaction-aware pooled DataSource objects, but the J2EE specification doesn't show you how, because it's outside the spec. If you browse the J2EE documentation, you won't find anything on how to create JDBC data sources. You'll have to look in the documentation for your container instead. Depending on your container, creating a data source might involve adding a data source definition to a property or configuration file, or might be done through a GUI administration tool.
Each container (or connection pool manager, like PoolMan) provides its own mechanism for creating a DataSource , and it is in this mechanism that the JTA magic is hidden. The connection pool manager obtains a Connection from the specified JDBC driver, but before returning it to the application, wraps it with a facade that also implements Connection , interposing itself between the application and the underlying connection. When the connection is created or a JDBC operation is performed, the wrapper asks the transaction manager if the current thread is executing in the context of a transaction, and automatically enlists the Connection in the transaction if one exists.
The other types of transactional resources, JMS message queues and JCA connectors, rely on a similar mechanism to hide resource enlistment from the user. When you make a JMS queue available to a J2EE application at deployment time, you again use a provider-specific mechanism to create the managed JMS objects (queue connection factories and destinations), which you then publish in a JNDI namespace. The managed objects created by the provider contain similar auto-enlistment code as the JDBC wrapper added by the container-supplied connection pool manager.
Transparent transaction control
The two types of J2EE transactions -- container-managed and bean-managed -- differ in how they start and end a transaction. Where a transaction starts and ends is referred to as transaction demarcation. The example code in Listing 1 demonstrates a bean-managed transaction (sometimes called a programmatic transaction.) Bean-managed transactions are started and ended explicitly by a component using the UserTransaction class. UserTransaction is made available to EJB components through theejbContext and to other J2EE components through JNDI.
Container-managed transactions (or declarative transactions) are started and ended transparently on behalf of the application by the container, based on transaction attributes in the component's deployment descriptor. You indicate whether an EJB component uses bean-managed or container-managed transactional support by setting the transaction-type attribute to eitherContainer or Bean .
With container-managed transactions, you can assign transactional attributes at either the EJB class or method levels; you can specify a default set of transactional attributes for the EJB class, and you can also specify attributes for each method if different methods are to have different transactional semantics. These transactional attributes are specified in the container-transaction section of the assembly descriptor. An example assembly descriptor is shown in Listing 2. The supported values for the trans-attribute are:
trans-attribute determines if the method supports execution within a transaction, what action the container should take when the method is called within a transaction, and what action the container should take if it is called outside of a transaction. The most common container-managed transaction attribute is Required . When Required is set, a transaction in process will enlist your bean in that transaction, but if no transaction is running, the container will start one for you. We will investigate the differences between the various transaction attributes, and when you might want to use each, in Part 3 of this series.
Listing 2. Sample EJB assembly descriptor
methodA() of object A starts a transaction, and then calls methodB() of object B , which acquires a JDBC connection and updates the database. The connection acquired by B will be automatically enlisted in the transaction created by A . How did the container know to do this?
When a transaction is initiated, the transaction context is associated with the executing thread. When A creates the transaction, the thread in which A is executing is associated with that transaction. Because local method invocations execute in the same thread as the caller, any methods called by A will also be in the context of that transaction.
Skeletons in the closet
What if object B is really a stub to an EJB component executing in another thread or even another JVM? Amazingly, resources accessed by remote object B will still be enlisted in the current transaction. The EJB object stub (the part that executes in the context of the caller), the EJB protocol (RMI over IIOP), and the skeleton object on the remote end all conspire to make this happen transparently. The stub determines if the caller is executing a transaction. If so, the transaction ID, or Xid, is propagated to the remote object as part of the IIOP call along with the method parameters. (IIOP is the CORBA remote-invocation protocol, which provides for propagating various elements of execution context, such as transaction context and security context; seeResources for more information on RMI over IIOP.) If the call is part of a transaction, the skeleton object on the remote system automatically sets the remote thread's transaction context, so that when the actual remote method is invoked, it is already part of the transaction. (The stub and skeleton objects also take care of beginning and committing container-managed transactions.)
Transactions can be initiated by any J2EE component -- an EJB component, a servlet, or a JSP page (or an application client, if the container supports it). This means that your application can start a transaction in a servlet or JSP page when a request arrives, do some processing within the servlet or JSP page, access entity beans and session beans on multiple servers as part of the page's logic, and have all of this work be part of one transaction, transparently. Figure 1 demonstrates how the transaction context follows the path of execution from servlet to EJB to EJB.
Figure 1. Multiple components in a single transaction
![]() Connection to the database; it may well be the case that each is accessing the exact same database. JTS can detect whether multiple resources are involved in the transaction or not, even if multiple connections are made to the same resource from different components, and optimize the execution of the transaction. As you'll recall from Part 1, involving multiple resources managers in a single transaction requires the use of the two-phase commit protocol, which is more expensive than the single-phase commit used by a single resource manager. JTS is able to determine if only a single resource manager is enlisted in a transaction. If it detects that all the resources involved in the transaction are the same, it can skip the two-phase commit and let the resource manager handle the transaction by itself.
Conclusion
The magic that allows for transparent transaction control, resource enlistment, and transaction propagation is not a part of JTS, but instead a part of how J2EE containers use JTA and JTS services behind the scenes on behalf of J2EE applications. There are many entities that conspire behind the scenes to make this magic happen transparently; the EJB stubs and skeletons, the JDBC driver wrappers provided by the container vendor, the JDBC drivers provided by the database vendor, the JMS providers, and the JCA connectors. All of these entities interact with the transaction manager so that your application code doesn't have to.
In Part 3, we'll look at some of the practical issues associated with managing transactions in a J2EE context -- transaction demarcation and isolation -- and their effects on application consistency, stability, and performance.
Resources
|
j2ee >