j2ee‎ > ‎

Java theory and practice: Understanding JTS -- The magic behind the scenes

posted Jun 20, 2010, 7:45 PM by Kuwon Kang
How J2EE containers hide the complexity of transaction management
Brian Goetz (brian@quiotix.com), Principal Consultant, Quiotix Corp
In Part 1 of this series, we examined transactions and explored their basic properties -- atomicity, consistency, isolation, and durability. Transactions are the basic building blocks of enterprise applications; without them, it would be nearly impossible to build fault-tolerant enterprise applications. Fortunately, the Java Transaction Service (JTS) and the J2EE container do much of the work of managing transactions for you automatically, so you don't have to integrate transaction awareness directly into your component code. The result is almost a kind of magic -- by following a few simple rules, a J2EE application can automatically gain transactional semantics with little or no additional component code. This article aims to demystify some of this magic by showing how and where the transaction management occurs. What is JTS? JTS is a component transaction monitor. What does that mean? In Part 1, we introduced the concept of a transaction processing monitor (TPM), a program that coordinates the execution of distributed transactions on behalf of an application. TPMs have been around for almost as long as databases; IBM first developed CICS, which is still used today, in the late 1960s. Classic (orprocedural) TPMs manage transactions defined procedurally as sequences of operations on transactional resources (such as databases). With the advent of distributed object protocols, such as CORBA, DCOM, and RMI, a more object-oriented view of transactions became desirable. Imparting transactional semantics to object-oriented components required an extension of the TPM model, in which transactions are instead defined in terms of invoking methods on transactional objects. JTS is just that: a component transaction monitor (sometimes called an object transaction monitor), or CTM. The design of JTS and J2EE's transaction support was heavily influenced by the CORBA Object Transaction Service (OTS). In fact, JTS implements OTS and acts as an interface between the Java Transaction API, a low-level API for defining transaction boundaries, and OTS. Using OTS instead of inventing a new object transaction protocol builds upon existing standards and opens the way for compatibility between J2EE and CORBA components. At first glance, the transition from procedural transaction monitors to CTMs seems to be only a change in terminology. However, the difference is more significant. When a transaction in a CTM commits or rolls back, all the changes made by the objects involved in the transaction are either committed or undone as a group. But how does a CTM know what the objects did during that transaction? Transactional components like EJB components don't have 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?

Back to top

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
  InitialContext ic = new InitialContext();
  UserTransaction ut = ejbContext.getUserTransaction();
  ut.begin();

  DataSource db1 = (DataSource) ic.lookup("java:comp/env/OrdersDB");
  DataSource db2 = (DataSource) ic.lookup("java:comp/env/InventoryDB");
  Connection con1 = db1.getConnection();
  Connection con2 = db2.getConnection();
  // perform updates to OrdersDB using connection con1
  // perform updates to InventoryDB using connection con2
  ut.commit();
Notice that there is no code in this example to enlist the JDBC connections in the current transaction -- the container does this for us. Let's look at how this happens. Three types of resource managers When an EJB component wants to access a database, a message queue server, or some other transactional resource, it acquires a connection to the resource manager (usually by using JNDI). Moreover, the J2EE specification only recognizes three types of transactional resources -- JDBC databases, JMS message queue servers, and "other transactional services accessed through JCA." Services in the latter class (such as ERP systems) must be accessed through JCA (the J2EE Connector Architecture). For each of these types of resources, either the container or the provider helps to enlist the resource into the transaction. In Listing 1, 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.

Back to top

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-transactionsection of the assembly descriptor. An example assembly descriptor is shown in Listing 2. The supported values for the trans-attribute are:
  • Supports
  • Required
  • RequiresNew
  • Mandatory
  • NotSupported
  • Never
The 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
<assembly-descriptor>
  ...
  <container-transaction>
    <method>
      <ejb-name>MyBean</ejb-name>
      <method-name>*</method-name>
    </method>
    <trans-attribute>Required</trans-attribute>
  </container-transaction>
  <container-transaction>
    <method>
      <ejb-name>MyBean</ejb-name>
      <method-name>updateName</method-name>
      </method>
   <trans-attribute>RequiresNew</trans-attribute>
  </container-transaction>
  ...
</assembly-descriptor>
Powerful, but dangerous Unlike the example in Listing 1, with declarative transaction demarcation there is no transaction management code in the component methods. Not only does this make the resulting component code easier to read (because it is not cluttered with transaction management code), but it has another, more significant advantage -- the transactional semantics of the component can be changed at application assembly time, without modifying or even accessing the source code for the component. While being able to specify transaction demarcation separate from the code is a very powerful feature, making poor decisions at assembly time can render your application unstable or seriously impair its performance. The responsibility for correctly demarcating container-managed transactions is shared between the component developer and the application assembler. The component developer needs to provide sufficient documentation as to what the component does, so that the application deployer can make intelligent decisions on how to structure the application's transactions. The application assembler needs to understand how the components in the application interact, so that transactions can be demarcated in a way that enforces application consistency and doesn't impair performance. We'll discuss these issues in Part 3 of this series.

Back to top

Transparent transaction propagation In either type of transaction, resource enlistment is transparent; the container automatically enlists any transactional resources used during the course of the transaction into the current transaction. This process extends not only to resources used by the transactional method, such as the database connections acquired in Listing 1, but also by methods it calls -- even remote methods. Let's take a look at how this happens. The container associates transactions with threads Let's say that 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 Multiple components in a single transaction

Back to top

Optimizations Having transactions be managed by the container allows the container to make certain optimization decisions for us. In Figure 1, we see a servlet and multiple EJB components access a database within the context of a single transaction. Each obtains aConnection 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.

Back to top

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
Comments