Transactions for J2EE are addressed by the Java Transaction Service (JTS) and Java Transaction API (JTA) specifications. The JTS specification defines five distinct players involved in transaction processing:
A transaction manager that provides services for transaction demarcation, resource management, synchronization, and transaction context propagation.
A resource manager that provides access to the underlying transactional resources. Examples of this are a database server, a message queue, or legacy systems.
An application server or TP monitor that provides the runtime environment for the applications and also manages the state of the transactions.
An application that operates in either a standalone mode or within the environment of an application server.
A communication resource manager (CRM) that will facilitate propagation of transaction context between multiple transaction managers.
We will break down the discussion of transactions in this environment into two distinct approaches — local versus global transactions.
If your transaction involves only a single transactional resource manager, then you have a local transaction. This is often the case when you are retrieving and modifying data in one database or when you send messages to one messaging queue. The transaction semantics are greatly reduced, as there is no need to coordinate the transaction with other resources. One option for local transactions is to use a database transaction rather than a JTA transaction. This is simpler and also involves less overhead. The database server has transaction support built in and this support is exposed via JDBC. All we need is to turn off auto-commit and call commit or rollback to complete the transaction. Later in this chapter, you will see how this can be leveraged without having to tie your code to the JDBC API.
A distributed or global transaction typically involves multiple resource managers and it also involves a transaction manager that coordinates the resources involved to make sure that the transaction is completed successfully by all involved resources. The transaction manager should use a two-phase commit protocol to make this reliable. The first phase consists of asking all the involved resources to prepare to commit. If the first phase completes and all resources are able to commit, then the second phase can continue with all resources committing the changes. If any resource failed to prepare in the first phase, then all resources will roll back any changes during the second phase.
Each transaction has some attributes associated with it, like its status. This information is stored in what is called the transaction context. This context must be made available to any transaction manager that handles this transaction. The transaction context is associated with the thread that is executing. In the case where there is a remote call when a transaction is active, the transaction context is passed along, propagated, to the remote process. This allows the remote process to participate in the same transaction.
The propagation of the transactional context is covered by JTS, which specifies the Java implementation of the OMG Object Transaction Service (OTS). A JTS transaction manager supports the JTA specification as the interface that application programs and application servers interact with. Some J2EE servers provide a JTS/JTA implementation that supports remote transaction propagation, but this is not a feature that is required by the J2EE specification.
Whether you use local or global transactions, you must somehow inform the transaction manager when your transaction begins and when it ends. This is called transaction demarcation and there are two ways to accomplish this. You can either do it programmatically by using the JTA or JDBC APIs, or you can take advantage of declarative transaction management that is offered by an EJB container as well as by the Spring Framework.
In your application program, you can programmatically demarcate transactions. This is possible both using direct transaction management with JDCB and using JTA directly. One drawback with this approach is that you tie your code to a specific strategy. You also tie your code to a specific transaction API. This reduces your options for future reuse of your code.
Declarative transactions provide a very attractive alternative to the programmatic solutions. Your code can now be transaction agnostic and you can allow a framework to demarcate the transactions. Not having to include transaction management in your code makes your code much more readable and easier to maintain. It also allows you to switch transaction strategy or implementation without changing your code. The EJB specification defines a number of transaction attributes that you specify in the deployment descriptor. The EJB transaction attributes for container-managed transaction demarcation are:
REQUIRED: This means that the method must participate in a transaction. A new transaction will be started if one is not already active.
REQUIRES NEW: A new transaction will always be started for this method. If there is an active transaction for the calling component, then that transaction is suspended until this method has completed.
NOT SUPPORTED: The method will not take part in any transactions. If there is one active in the calling component, then it is suspended while this method is processing. The suspended transaction is resumed once this method has completed.
SUPPORTS: There is no requirement that this method should be executed in a transaction. If one is already started, then this method will take part in that transaction.
MANDATORY: The calling component must already have an active transaction that this method will take part in.
NEVER: This method is not participating in a transaction and it is also required that there is not an active transaction for the calling component.
Later in this chapter, we will see the same attributes used by Spring's declarative transaction implementation as well.