Special Edition Using Enterprise JavaBeans 2.0
Programming with the Java Transaction API
With a general understanding of transactions under your belt, it's time to look more at how transactions are supported by the J2EE platform. To provide the transactional capabilities needed by a J2EE application, an EJB container is required to support the high-level interface defined by the Java Transaction API (JTA) for transaction demarcation . Don't worry if you don't know what that means just yet. The best way to understand JTA and its role in EJB is to look at how it relates to several key specifications on transaction processing. This background will help you see the big picture into which JTA fits and get a quick introduction to the terminology involved in describing transactional systems. The Object Transaction Service
The worlds of object-oriented development and transactions were brought together by the Object Management Group's (OMG) specification of the CORBA Object Transaction Service (OTS) in 1994. If you've done any CORBA development, you're already quite familiar with OMG and their work in the distributed object arena. The goal of OTS was to define a set of interfaces that could support performing a transaction across multiple distributed objects. Transactions were nothing new to software at this point, but this effort was needed to apply the benefits of object-oriented development to transactional distributed processing. This work was done in conjunction with the international open systems organization X/Open to build on their Distributed Transaction Processing (DTP) model. You'll see X/Open referenced in much of the literature on transactions, but the organization has since become known as the Open Group. The DTP is a model that came out of the Open Group vendor consortium and became the accepted standard for commercial database servers and other transactional systems. It defines how a transactional application interacts with components designated as resource managers and transaction managers to execute distributed transactions. You need to understand these two terms to see where responsibilities are placed within an architecture such as EJB to manage transactions. To start with the basics, you must be clear on what constitutes a resource. A system that provides data and information services to an application is referred to as an Enterprise Information System (EIS) within J2EE. EIS examples include relational databases and ERP systems. Using the terminology of this chapter, an EIS isn't a resource itself, but it does contain and manage resources. For example, the rows of data held in a database and the business objects provided by an ERP system can be considered resources. A resource manager is a system, such as a database server, an ERP, or a message queue, that makes one or more resources accessible to an application. An application communicates with a resource manager using an adapter (or driver) provided by the resource manager. A JDBC driver is the resource manager adapter you see most often when doing Java development. This particular type of adapter is a vendor-specific (meaning resource manager specific in this case) Java class that allows a client application to communicate with a certain type of database server and access data in a database (the resource). For the purposes of this chapter, the resource managers of the most interest are the ones that can participate in transactions that are controlled by an external transaction manager. These usually are known as transactional resource managers . A transaction manager is a component responsible for providing the low-level transaction interaction between an application and one or more resource managers. A transaction manager allows resource managers to associate their resources with a transaction through a process called enlistment . The transaction manager provides the demarcation of a transaction by notifying the enlisted resources when the transaction starts, maintaining the associations between the transaction and its enlisted resources until it ends (by committing or rolling back), and then allowing the resources to delist themselves . The transaction manager plays the role of traffic cop in some respects. For example, if a transaction that includes an update to a row in a database needs to be rolled back, the transaction manager is responsible for directing what takes place but not for actually doing it. When directed to perform a rollback by the transaction manager, it is the responsibility of the resource manager (the database server, in this example) to roll back the update. A resource manager actually " manages " the consistency of its resources. A transaction manager provides instructions, but the individual resource managers are responsible for performing the commits and rollbacks that affect their resources. A transactional resource manager must support two types of interfaces: one for use by applications and one for use by transaction managers. The application interface is likely more familiar to you because, in the case of relational databases, it includes functionality such as acquiring a connection and issuing SQL statements. The X/Open DTP model doesn't address this interface because it's specific to the type of resource manager your application is accessing. To interact with a transaction manager, a resource manager also must provide an interface that can be used to associate a resource with a transaction, inform the resource manager that a transaction needs to be committed or rolled back, and incorporate the resource manager in a two-phase commit operation. A two-phase commit allows a transaction manager to include several resource managers in a transaction while ensuring that either every associated resource is updated when a commit is attempted or none of them are. This strategy consists of instructing each resource manager to prepare to commit the current transaction and report back to the transaction manager if a permanent commit of the changes will be successful. If every enlisted resource votes to proceed, the transaction manager issues the command to perform the actual commit. Otherwise, each resource manager is instructed to roll back the transaction. Some definite overhead is associated with a two-phase commit because a resource manager that reports that it can proceed must maintain any state related to its affected resources until the command to complete the transaction is received. For example, it might have to keep any locks or persist temporary data used to recover from an operation. The interface a resource manager exposes for use by transaction managers is defined by the X/Open XA interface specification. This is where the operations needed to associate a resource with a transaction and perform two-phase commit requests are found. X/Open XA is a two-way interface that also specifies the operations that a transaction manager must expose to resource managers. This side of the XA interface consists of the two functions a resource manager calls to enlist itself in a transaction and later delist itself. The interface a transaction manager implements for use by an application is defined by the X/Open TX specification. The TX interface gives the application the access it needs to control transaction demarcation and a few other options such as defining a transaction timeout value. You'll see more about this interface later in the chapter. Figure 12.1 identifies the interfaces between the components defined by the DTP model. Figure 12.1. The X/Open Distributed Transaction Processing model defines the participants in a distributed transaction.
When an application needs to execute a transaction, it sends a request to a transaction manager. The transaction manager is then responsible for starting the transaction and associating it with a transaction context that is propagated to each of the resource managers participating in the transaction. This transaction context remains valid until the transaction is either committed or rolled back under the direction of the transaction manager. A variant of this process is the concept of a heuristic decision, which refers to one or more of the resource managers in a transaction making the decision to commit or roll back without being directed to do so by the transaction manager. This is not typical and usually occurs only in situations such as a communications failure that prevents the normal interaction with the transaction manager from taking place. If you consider the ACID characteristics required of a transaction, you can see the danger in a heuristic decision occurring. If some resources in a transaction commit and others don't, the operation is no longer atomic and data consistency has likely been lost. Because this can quickly lead to integrity problems, the DTP includes the capability to report the occurrence of a heuristic decision. This is carried forward in OTS by several exceptions that a resource manager can throw to indicate that a heuristic decision has played a part in a commit or rollback. If you understand the components and interactions defined by DTP, understanding OTS only requires that you think of all the components as distributed objects. OTS extends the DTP model by specifying the implementation of the XA and TX interfaces using CORBA IDL and by specifying IIOP as the communications backbone to be used for interactions between the objects involved. From the specification, the purpose of the transaction service defined by OTS is then to
The OTS specification also defines nested transactions and a synchronization interface as optional features of an implementation. A nested transaction is a set of operations associated with a parent transaction that can be committed or rolled back as a unit but will only be made permanent if the parent transaction is eventually committed as well. Transactions that treat all operations as part of a single, non-nested unit are called flat transactions . The synchronization interface defined by OTS can be used by a transaction manager to notify a registered object when a commit is about to occur or when a commit or rollback has just completed.
Note You can download a copy of the OTS specification from the formal specifications section of OMG's Web site, http://www.omg.org/. After its initial release, the specification was repackaged as the Transaction Service Specification v1.1, but the content remained the same. At the time of this writing, the current release was version 1.2.
The Java Transaction API
The JTA defines a set of Java interfaces that specify how a transaction manager communicates with an application, a resource manager, and the application server in a J2EE architecture. The primary interfaces found in the JTA consist of
Figure 12.2 illustrates where the various interfaces come into play. It's important to remember here that JTA defines these interfaces but it doesn't include implementations for any of them. It's up to the application server and resource adapter vendors to implement the parts of JTA that apply to them. Figure 12.2. The Java Transaction API specifies a set of Java interfaces that define how the components in a transaction communicate.
Note As shown in Figure 12.2, an application server and a transaction manager are viewed as separate components. However, application server vendors include a transaction manager as part of their products. This chapter discusses the division of responsibilities between the two, but, as an EJB developer, you usually can view them as a single component of your systems.
JTA includes a few supporting interfaces beyond those shown in Figure 12.2 and several exception classes. For example, the javax.transaction package declares HeuristicCommitException , HeuristicMixedException , and HeuristicRollbackException to report heuristic decisions that affect the outcome of a transaction using the mechanism defined by OTS. Of the interfaces declared by JTA, the three that have already been mentioned are the most important. For the discussion here, UserTransaction is of the most interest because it is the only part of JTA that an EJB container is specifically required to support. This interface is made up of the methods shown in the following declaration: public interface UserTransaction { public void begin() throws NotSupportedException, SystemException; public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, java.lang.SecurityException, java.lang.IllegalStateException, SystemException; public void rollback() throws java.lang.IllegalStateException, java.lang.SecurityException, SystemException; public void setRollbackOnly() throws java.lang.IllegalStateException, SystemException; public int getStatus() throws SystemException; public void setTransactionTimeout(int seconds) throws SystemException; } UserTransaction is supported by the javax.transaction.Status interface, which defines the following constants that can be used to interpret the values returned from the getStatus method: public interface Status { public final static int STATUS_ACTIVE; public final static int STATUS_COMMITTED; public final static int STATUS_COMMITTING; public final static int STATUS_MARKED_ROLLBACK; public final static int STATUS_NO_TRANSACTION; public final static int STATUS_PREPARED; public final static int STATUS_PREPARING; public final static int STATUS_ROLLEDBACK; public final static int STATUS_ROLLING_BACK; public final static int STATUS_UNKNOWN; } From an application programming standpoint, the UserTransaction interface is all you need from JTA to communicate with a transaction manager to control distributed transactions. The rest of JTA defines lower-level interactions that are needed only by the application server. You can programmatically demarcate transactions using the methods defined by UserTransaction . As an alternative, you can instead use a declarative approach that instructs the EJB container to transparently manage demarcation for you. You'll see more about each of these approaches in the next two sections. A transaction managed by the J2EE platform is referred to as a JTA transaction . You'll also see the terms global transaction and XA transaction used. The transaction manager associates a transaction context with each calling thread that either references the caller's JTA transaction or is set to null if there is no associated transaction. The context is held by an object that implements the javax.transaction.Transaction interface. This JTA interface is used under the hood to perform steps such as enlisting resources with a transaction and instructing it to commit or roll back. Transaction declares the following methods: public interface Transaction { public void commit() throws RollbackException, HeuristicMixedException, HeuristicRollbackException, java.lang.SecurityException, SystemException; public boolean delistResource(XAResource xaRes, int flag) throws java.lang.IllegalStateException, SystemException; public boolean enlistResource(XAResource xaRes) throws RollbackException, java.lang.IllegalStateException, SystemException; public int getStatus() throws SystemException; public void registerSynchronization(Synchronization sync) throws RollbackException, java.lang.IllegalStateException, SystemException; public void rollback() throws java.lang.IllegalStateException, SystemException; public void setRollbackOnly() throws java.lang.IllegalStateException, SystemException; } You can start a JTA transaction using UserTransaction or the EJB container can do it through TransactionManager . Both of these interfaces declare a begin method for this purpose. Regardless of how it is started, a JTA transaction is associated with the calling thread and is automatically propagated between components in your application and any resources that are enlisted with the transaction. The transaction manager communicates with enlisted resource managers through the methods of XAResource . When a resource is first accessed within a given transaction, an XAResource reference is obtained by the application server. This is accomplished with a call to a method such as the getXAResource method declared by the javax.sql.XAConnection interface that can be implemented by JDBC drivers. The application server associates the resource manager that this XAResource represents with a transaction by calling the enlistResource method of Transaction . The transaction manager then calls the start method of the XAResource with an identifier for the transaction. At this point, the application server supplies a connection to the resource manager to the application. All operations performed through this connection are associated with the transaction. After the desired work has been performed and a request is made by the application or the container to commit the transaction, the transaction manager calls the end method on the XAResource followed by its prepare and commit methods ( assuming a two-phase commit is being performed).
Note The javax.sql.XAConnection and javax.sql.XADataSource interfaces were introduced as part of JDBC 2.0 to allow JDBC drivers to support distributed transactions. An object that implements XADataSource serves as a factory for XAConnection objects.
Although the preceding sequence of steps referred to only a single resource manager, the value of XAResource is realized most when multiple resource managers are enlisted with a transaction. It's in this situation that a two-phase commit is beneficial. If only a single resource manager is accessed within a transaction, the commit or rollback of the transaction can be executed by that resource manager without the need to perform a two-phase commit. This is true even if multiple application servers access a single resource manager, such as a database, as part of the same transaction. What matters is the number of resource managers involved and the transaction manager's ability to recognize that all access to the resource is part of the same transaction. As with anything of value, a two-phase commit has costs associated with it. As mentioned previously, the work required by a resource manager to prepare for a commit and report its status back to the transaction manager has a certain amount of overhead associated with it. If a two-phase commit isn't necessary, the transaction manager can improve performance by bypassing the call to prepare and instructing the single XAResource to simply commit its work. This optimization, which is referred to as one-phase commit, is a required capability for transaction managers that satisfies the J2EE Connector Architecture Specification. Key to EJB's capability to execute distributed transactions is its facility for propagating the transaction context between components. For example, if a transaction is started to support the work of a session bean method that accesses several entity bean instances to modify an underlying database, the transaction context is associated with each bean instance and the database operations that are executed without any explicit programming on your part. This is known as implicit propagation and it is another way that the EJB architecture lightens the burden on you as a developer. A JTA transaction is not limited to propagation across application tier components, but can also include a Java client application or JSPs and servlets and their supporting classes. Combining multiple components and resources under a single JTA transaction is obviously a complex feat, but to you, it's transparent. The opposite of a JTA (or global) transaction is a local transaction. A local transaction describes the behavior of a resource manager when it directs its commit and rollback decisions without input from an external transaction manager. When working with multiple resource managers, a JTA transaction provides the coordination across distributed resources needed by your applications. When a resource manager executes a local transaction, updates to the associated resources are committed or rolled back independently of any other components and resources accessed by the application. If a JTA transaction is used, the work done by all resource managers enlisted with the transaction can be managed collectively and either committed or rolled back as an atomic unit.
Note Use of local transactions is discouraged only when multiple resource managers are being accessed by an application. When a single resource manager is being used, a local transaction can be a valuable performance optimization. As mentioned previously, using a one-phase commit optimization is one option a transaction manager can use to improve performance when working with a single resource manager. As an alternative, a container can avoid the overhead of the transaction manager altogether and instruct the resource manager to use a local transaction. If the container provides either of these optimizations, they are handled transparently to you as an application developer.
Although most examples of transactions are based on accessing relational databases, it's important to remember that a transaction can be associated with other types of resource managers. Besides JDBC connections, a JTA transaction can apply to any resource with an adapter that supports the XAResource interface. Of particular interest to J2EE application development is that a JMS Session can be tied to a transaction. When a Session is part of a transaction, the messages it sends and receives during the extent of the transaction are treated as an atomic unit of work. If the transaction is committed, any messages received during the transaction are acknowledged and any output messages are sent. If the transaction is rolled back, any received messages are recovered and any output messages are destroyed . The Session interface provides this basic functionality that can be used to transact a JMS session locally. To be included in a JTA transaction that includes other resources, however, the JMS provider must support JTA by implementing its sessions so that they satisfy the javax.jms.XASession interface as well. This variety of session is obtained from a javax.jms.XAConnection and provides a corresponding XAResource that can be used by an application server to associate its message production and consumption with a transaction. JTA and the Java Transaction Service
To help you keep the acronyms straight that you will undoubtedly encounter when doing EJB development, you should have some understanding of the Java Transaction Service (JTS) and how it relates to JTA. Because OMG's OTS is a language-neutral specification, it needs to be addressed for particular programming languages. JTS does that by specifying a Java implementation of a transaction manager that satisfies the OTS 1.1 specification. This part of JTS is viewed as the low-level details of an implementation. At the high-level, JTS supports the JTA interfaces (remember that JTA by itself doesn't include implementations of any of its interfaces). Basically, JTS specifies an implementation of JTA that is based on OTS. An application server might implement JTS as a way to satisfy its need for JTA, but it isn't required to choose this or any other implementation approach. Whether a server implements JTS or not is unimportant to you as an application developer. You should view JTA as the API available to you and the EJB container to support transaction processing. Separating this from the other parts of JTS that are required to support OTS simplifies what you need to understand to develop transactional applications. Your components should never attempt to interact with the rest of JTS.
Note This chapter focuses on what you need to know about JTA as an application developer using EJBs. If you're interested in more of the details behind this API, you can download both the JTA and the JTS specifications from Sun at http://www.java.sun. com/products/. Also useful in understanding the relationships between the various components involved in a transaction is the J2EE Connector Architecture Specification, which is available from Sun as well.
|