This document is an update to the BES 5.2 Developer's Guide chapter of the same name. It contains the following sections:
Application programmers benefit from developing their applications on platforms such as Java 2 Enterprise Edition (J2EE) that support transactions. A transaction-based system simplifies application development because it frees the developer from the complex issues of failure recovery and multi-user programming. Transactions are not limited to single databases or single sites. Distributed transactions can simultaneously update multiple databases across multiple sites.
A programmer typically divides the total work of an application into a series of units. Each unit of work is a separate transaction. As the application progresses, the underlying system ensures that each unit of work, each transaction, fully completes without interference from other processes. If not, it rolls back the transaction and completely undoes whatever work the transaction had performed.
Typically, transactions refer to operations that access a shared resource like a database. All access to a database is performed in the context of a transaction. All transactions share the following characteristics:
These characteristics are denoted by the acronym ACID.
A transaction often consists of more than a single operation. Atomicity requires that either all or none of the operations of a transaction are performed for the transaction to be considered complete. If any of a transaction's operations cannot be performed, then none of them can be performed.
Consistency refers to resource consistency. A transaction must transition the database from one consistent state to another. The transaction must preserve the database's semantic and physical integrity.
Isolation requires that each transaction appear to be the only transaction currently manipulating the database. Other transactions can run concurrently. However, a transaction must not see the intermediate data manipulations of other transactions until and unless they successfully complete and commit their work. Because of interdependencies among updates, a transaction can get an inconsistent view of the database were it to see just a subset of another transaction's updates. Isolation protects a transaction from this sort of data inconsistency.
Transaction isolation is qualified by varying levels of concurrency permitted by the database. The higher the isolation level, the more limited the concurrency extent. The highest level of isolation occurs when all transactions can be serialized. That is, the database contents look as if each transaction ran by itself to completion before the next transaction started. However, some applications can tolerate a reduced level of isolation for a higher degree of concurrency. Typically, these applications run a greater number of concurrent transactions even if transactions are reading data that may be partially updated and perhaps inconsistent.
Lastly, durability means that updates made by committed transactions persist in the database regardless of failure conditions. Durability guarantees that committed updates remain in the database despite failures that occur after the commit operation and that databases can be recovered after a system or media failure.
BES supports flat transactions, but not nested transactions. Transactions are implicitly propagated. This means that the user does not have to explicitly pass the transaction context as a parameter, because the J2EE container transparently handles this for the client.
Transaction management can be performed programmatically by calling the standard JTS or JTA APIs. An alternative, and more recommended approach, when writing J2EE components such as Enterprise JavaBeans (EJBs) is to use declarative transactions where the J2EE Container transparently starts and stops transactions.
There are two transaction managers, or engines, available in BES:
A Partition Transaction Service exists in each Borland Enterprise Server partition. It is a Java implementation of the CORBA Transaction Service Specification. The Partition Transaction Service supports transaction timeouts, one-phase commit protocol and can be used in a two-phase commit protocol under special circumstances.
Use the Partition Transaction Service under the following conditions:
EJBAllowUnrecoverableCompletion for the partition.The 2PC Transaction Service exists in a separate address space. It provides a complete solution for distributed transactional CORBA applications. Implemented on top of the VisiBroker ORB, the 2PC Transaction Service simplifies the complexity of distributed transactions by providing an essential set of servicesincluding a transaction service, recovery and logging, integration with databases, and administration facilitieswithin one, integrated architecture.
The Borland EJB Container supports distributed transactions. Distributed transactions are those transactions that cross systems, platforms, and Java Virtual Machines (JVMs).
Transactions that manipulate data across multiple databases use a two-phase commit process. This process ensures that the transaction correctly updates all databases involved in the transaction. If it cannot update all databases, then it updates none of the databases.
Note: Although support is provided by BES for two-phase commit transactions, they are inherently expensive due to number of remote procedure calls (RPCs) and perhaps should be used only when needed. See the next section, When to use two-phase commit transactions.
There are two steps to a two-phase commit. The first step is the preparation phase. In this phase the transaction service requests that each database involved in the transaction readies its updates and signal to the transaction service whether it can commit the updates. The second step is the commit phase. The transaction service initiates the actual database updates only when all databases have signaled that they can complete the update process. Should any of the databases signal that they cannot perform their updates, the transaction service instructs all databases to rollback all updates involved in the transaction.
The Partition Transaction and 2PC Transaction managers support both heterogeneous distributed (two-phase commit) transactions and two-phase commit for homogeneous databases.
By default, the Partition Transaction Service does not allow multiple resources to participate in a global transaction, though it does support a nonrecoverable two-phase commit process. You must enable unrecoverable completion on the Partition Transaction Service to support a nonrecoverable two-phase commit. When enabled, the container makes a one-phase commit call on each participating database during the transaction commit process. Care must taken when enabling unrecoverable completion, setting to true either system property EJBAllowUnrecoverableCompletion or "Allow unrecoverable completion" partition property from the BES console, because there is no recovery if a failure occurs during a two-phase commit transaction.
To support heterogeneous two-phase commit transactions, the 2PC Transaction Service must integrate with XA support in the underlying DBMS. (See the JDBC specification for more information.) With availability of XA-enabled JDBC drivers from DBMS vendors, the EJB container and 2PC Transaction Service allow multiple databases to participate in a single transaction.
Two-phase commit for homogeneous databases requires some configuration of the DBMS server. While the container controls the commit to the first database, the DBMS server controls the commits to the subsequent databases using the DBMS's built-in transaction coordinator. For more information, see your vendor's manual for the DBMS server.
One of the basic principals of building high performance distributed applications is to limit the number of remote procedure calls (RPCs). The following explains typical situations--when and when not to use two-phase commit transactions. Avoiding a two-phase commit transaction when it is not needed, therefore avoiding unnecessary RPCs, greatly improves your application's performance.
Using multiple database resources from a single vendor in the same transaction
If multiple databases from a single vendor are used, it is often possible to avoid using two-phase commit. You can access one database and use that database to access the second by tunneling access to the second through the connection to the first database. Oracle and other DBMSs provide this capability. In this case there would be only one JDBC connection to the "fronting" database. Access to the "backing" database is tunneled through that first JDBC connection.
Using two JDBC connections to the same database resource in the same transaction
In the much more common case of having two JDBC connections to the same database all within a single transaction, you need XA and one-phase commit, which is done by activating the Partition Transaction Service. This can also be achieved by collocating the beans that access the database into a single EJB container and not using XA at all.
Using multiple disparate resources in a single transaction
In this case there truly is a need for a two-phase commit transaction. This is needed if, for example, you are running a single transaction against both Oracle and Sybase, or if you have a transaction that accesses MQSeries via JMS and also accesses Oracle. In this case, the transaction has to be coordinated via the XA interfaces (using the XA-enabled JMS driver and an XA-enabled JDBC driver) and the resources (MQ, Oracle) both participate in the two-phase commit transaction. It is worth noting that two-phase commit capabilities (provided by the 2PC Transaction Service), are only needed when there is a single transaction accessing and involving multiple incompatible resources.
Transaction management for Enterprise JavaBeans (EJBs) is handled by the EJB Container and the EJBs. Enterprise JavaBeans make it possible for applications to update data in multiple databases within a single transaction.
EJBs utilize a declarative style of transaction management that differs from the traditional transaction management style. With declarative management, the EJB declares its transaction attributes at deployment time. The transaction attributes indicate whether the EJB container manages the bean's transactions or whether the bean itself manages its own transactions, and, if so, to what extent it does its own transaction management.
Traditionally, the application was responsible for managing all aspects of a transaction. This entailed such operations as:
It requires a developer with extensive transaction processing expertise to write an application that is responsible for managing a transaction from start to finish. The code for such an application is more complex and difficult to write, and it is easy for "pilot error" to occur.
With declarative transaction management, the EJB container manages most if not all aspects of the transaction for you. The EJB container handles starting and ending the transaction, plus maintains its context throughout the life of the transaction object. This greatly simplifies an application developer's responsibilities and tasks, especially for transactions in distributed environments.
When an EJB programmatically performs its own transaction demarcation as part of its business methods, then that bean is considered to be using bean-managed transaction. On the other hand, when the bean defers all transaction demarcation to its EJB container, and the container performs the transaction demarcation based on the Application Assembler's deployment instructions, then the bean is referred to as using container-managed transaction.
EJB session beans, both stateful and stateless varieties, can use either container- or bean-managed transactions. However, a bean cannot use both types of transaction management at the same time. EJB entity beans can only use container-managed transaction. It is the bean provider who decides the type of transaction which an EJB can use.
An EJB can manage its own transaction if it wishes to start a transaction as part of one operation and then finish the transaction as part of another operation. However, such a design might be problematic if one operation calls the transaction starting method, but no operation calls the transaction ending method.
Whenever possible, enterprise beans should use container-managed transactions as opposed to bean-managed transactions. Container-managed transactions require less programming work and are less prone to programming error. In addition, a container-managed transaction bean is easier to customize and compose with other beans.
A local transaction is a transaction that is managed by the resource manager such as that associated with a database. A global transaction, on the other hand, is a transaction managed by the Partition Transaction Service or the 2PC Transaction Service which are global transaction managers.
An EJB that uses bean-managed transaction demarcation uses the javax.transaction.UserTransaction interface to explicitly demarcate global transaction boundaries. When transaction demarcation is container managed, the container interposes each client call to control the transaction demarcation. It then controls this demarcation declaratively, according to the transaction attribute set by the Application Assembler in the deployment descriptor. The transaction attribute also determines whether the transaction is local or global.
The EJB container follows certain rules to determine when it is to do a local versus a global transaction for container-managed transactions. In general, a container calls the method within a local transaction after verifying that no global transaction already exists. It also verifies that it is not expected to start a new global transaction and that the transaction attributes are set for container-managed transactions. The container automatically wraps a method invocation within a local transaction if one of the following is true:
The EJB container supports the following characteristics for local transactions:
javax.ejb.EJBContext methods setRollbackOnly() and getRollbackOnly().
EJBs that use bean-managed transaction have transaction attributes associated with each method of the bean. The attribute value tells the container how it must manage the transactions that involve this bean. There are six different transaction attributes that can be associated with each method of a bean. This association is done at deployment time by the Application Assembler or Deployer.
These attributes are:
RequiredThis attribute guarantees that the work performed by the associated method is within a global transaction context. If the caller already has a transaction context, then the container uses the same context. If not, the container begins a new transaction automatically. This attribute permits easy composition of multiple beans and co-ordination of the work of all the beans using the same global transaction.RequiresNewThis attribute is used when the method does not want to be associated with an existing transaction. It ensures that the container begins a new transaction.SupportsThis attribute permits the method to avoid using a global transaction. This must only be used when a bean's method only accesses one transaction resourceor no transaction resourcesand does not invoke another enterprise bean. It is used solely for optimization, because it avoids the cost associated with global transactions. When this attribute is set and there is already a global transaction, the EJB Container invokes the method and have it join the existing global transaction. However, if this attribute is set, but there is no existing global transaction, the Container starts a local transaction for the method, and that local transaction completes at the end of the method.NotSupportedThis attribute also permits the bean to avoid using a global transaction. When this attribute is set, the method must not be in a global transaction. Instead, the EJB Container suspends any existing global transaction and starts a local transaction for the method, and the local transaction completes at the conclusion of the method.MandatoryIt is recommended that this attribute not be used. Its behavior is similar to Requires, but the caller must already have an associated transaction. If not, the container throws a javax.transaction.TransactionRequiredException. This attribute makes the bean less flexible for composition because it makes assumptions about the caller's transaction.NeverIt is recommended that this attribute not be used. However,
if used, the EJB Container starts a local transaction for the method. The local transaction completes at the conclusion of the method.Under normal circumstances only two attributes, Required and RequiresNew, must be used. The attributes Supports and NotSupported are strictly for optimization. The use of Never and Mandatory are not recommended because they affect the composibility of the bean. In addition, if a bean is concerned about transaction synchronization and implements the javax.ejb.SessionSynchronization interface, then the Assembler/Deployer can specify only the attributes Required, RequiresNew, or Mandatory. These attributes ensure that the container invokes the bean only within a global transaction, because transaction synchronization can only occur within a global transaction.
All transactions use the Java Transaction API (JTA). When transactions are container managed, the platform handles the demarcation of transaction boundaries and the container uses the JTA API; you do not need to use this API in your bean code.
A bean that manages its own transactions (bean-managed transaction), however, must use the JTA javax.transaction.UserTransaction interface. This interface allows a client or component to demarcate transaction boundaries. Enterprise JavaBeans that use bean-managed transactions use the method EJBContext.getUserTransaction().
In addition, all transactional clients use JNDI to look up the UserTransaction interface. This simply involves constructing a JNDI InitialContext using the JNDI naming service, as shown in the following line of code:
javax.naming.Context context = new javax.naming.InitialContext();
Once the bean has obtained the InitialContext object, it can then use the JNDI lookup() operation to obtain the UserTransaction interface, as shown in the following code sample.
javax.transaction.UserTransaction utx = (javax.transaction.UserTransaction)
context.lookup("java:comp/UserTransaction");
Note that an EJB can obtain a reference to the UserTransaction interface from the EJBContext object. This is because an enterprise bean by default inherits a reference to the EJBContext object. Thus, the bean can simply use the EJBContext.getUserTransaction() method rather than having to obtain an InitialContext object and then using the JNDI lookup() method. However, a transactional client that is not an enterprise bean must use the JNDI lookup approach.
When the bean or client has the reference to the UserTransaction interface, it can then initiate its own transactions and manage these transactions. That is, you can use the UserTransaction interface methods to begin and commit (or rollback) transactions. You use the begin() method to start the transaction, then the commit() method to commit the changes to the database. Or, you use the rollback() method to abort all changes made within the transaction and restore the database to the state it was in prior to the start of the transaction. Between the begin() and commit() methods, you include code to carry out the transaction's business.
The standard Java Database Connectivity (JDBC) API is used by BES to access databases that support JDBC through vendor provided drivers. Requests for access to a database is centralized through the BES JDBC Connection Pool. This section describes modifications the BES JDBC pool makes to JDBC behavior for transactions.
The JDBC pool is a pseudo JDBC driver that allows a transactional application to obtain a JDBC connection to a database. The JDBC pool associates JDBC connections with the partition transaction service's transactions, and delegates connection requests to JDBC drivers that factory the JDBC connections. Once a connection is obtained using the JDBC pool, the transaction is coordinated automatically by the transaction service.
The JDBC pool and its associated resources provide complete transactional access to the DBMS. The JDBC pool registers resources transparently with the transaction coordinator. Because of limitations of the 1.x version of the JDBC API, the JDBC pool can only provide one-phase commit. Version 2.0 of the JDBC API supports full two-phase commit.
To enable JDBC access for transactional applications written in Java, you use the JDBC API. The JDBC API is fully documented at the following web site:
http://www.javasoft.com/products/jdk/1.2/docs/guide/jdbc/spec/jdbc-spec.frame.html
However, the behavior of some JDBC methods is overridden by the partition's transaction service when they are invoked within the context of a transaction managed by the partition. The following methods are affected:
Java.sql.Connection.commit()Java.sql.Connection.rollback()Java.sql.Connection.close()Java.sql.setAutoCommit(boolean)The rest of this section explains the changes to the semantics of these methods for partition-managed transactions.
Note: If a thread is not associated with a transaction, all of these methods will use the standard JDBC transaction semantics.
Java.sql.Connection.commit()
As defined in the JDBC API, this method commits all work that was performed on a JDBC connection since the previous commit() or rollback(), and releases all database locks.
If a global transaction is associated with the current thread of execution do not use this method. If the global transaction is not a container-managed transaction, that is the application manages its own transactions, and a commit is required use the JTA API to perform the commit rather than invoking commit() directly on the JDBC connection.
Java.sql.Connection.rollback()
As defined in the JDBC API, this method rolls back all work that was performed on a JDBC connection since the previous commit() or rollback(), and releases all database locks.
If a global transaction is associated with the current thread of execution do not use this method. If the global transaction is not a container-managed transaction, that is the application manages its own transactions, and a rollback is required use the JTA API to perform the rollback rather than invoking rollback() directly on the JDBC connection.
Java.sql.Connection.close()
As defined in the JDBC API, this method closes the database connection and all JDBC resources associated with the connection.
If the thread is associated with a transaction this call simply notifies the JDBC pool that work on the connection is complete. The JDBC pool releases the connection back to the connection pool once the transaction has completed. JDBC connections opened by the JDBC pool cannot be closed explicitly by an application.
Java.sql.Connection.setAutoCommit(boolean)
As defined in the JDBC API, this method is used to set the auto commit mode of a transaction. The setAutoCommit() method allows Java applications to either:
commit() or rollback() on the connection (when set to false).If the thread is associated with a transaction, the JDBC pool turns off the auto-commit mode for all connections factoried in the scope of a partition's transaction service transaction. This is because the transaction service must control transaction completion. If an application is involved with a transaction, and it attempts to set the auto commit mode to true, the java.sql.SQLException() will be raised.
Enterprise JavaBeans can throw application and/or system level exceptions if they encounter errors while handling transactions. Application-level exceptions pertain to errors in the business logic and are intended to be handled by the calling application. System-level exceptions, such as runtime errors, transcend the application itself and can be handled by the application, the bean, or the bean container.
The EJB declares application-level exceptions and system-level exceptions in the throws clauses of its Home and Remote interfaces. You must check for checked exceptions in your program try/catch block when calling bean methods.
An EJB throws a system-level exception, which is a java.ejb.EJBException (but may also be a java.rmi.RemoteException), to indicate an unexpected system-level failure. For example, it throws this exception if it cannot open a database connection. The java.ejb.EJBException is a runtime exception and does not have to be listed in the throws clause of the bean's business methods.
System-level exceptions usually require the transaction to be rolled back. Often, the container managing the bean does the rollback. Other times, especially with bean-managed transactions, the client must rollback the transaction.
An EJB throws an application-level exception to indicate application-specific error conditions, that is, business logic errors and not system problems. These application-level exceptions are exceptions other than java.ejb.EJBException. Application-level exceptions are checked exceptions, which means you must check for them when you call a method that potentially can throw this exception.
The EJB's business methods use application exceptions to report abnormal application conditions, such as unacceptable input values or amounts beyond acceptable limits. For example, a bean method that debits an account balance can throw an application exception to report that the account balance is not sufficient to permit a particular debit operation. A client can often recover from these application-level errors without having to rollback the entire transaction.
The application or calling program gets back the same exception that was thrown and this allows the calling program to know the precise nature of the problem. When an application-level exception occurs, the EJB instance does not automatically rollback the client's transaction. The client now has the knowledge and the opportunity to evaluate the error message, take the necessary steps to correct the situation, and recover the transaction. Otherwise, the client can abort the transaction.
Because application-level exceptions report business logic errors, the client is expected to handle these exceptions. While these exceptions can require transaction rollback, they do not automatically mark the transaction for rollback. You often have the option to retry the transaction, though there are times when you must abort and rollback the transaction.
The bean Provider is responsible for ensuring that the state of the bean is such that, if the client continues with the transaction, there is no loss of data integrity. If the Provider cannot ensure this degree of integrity, then the bean marks the transaction for rollback.
When your client program gets an application exception, you must first check if the current transaction has been marked for "rollback only". For example, a client can receive a javax.transaction.TransactionRolledbackException. This exception indicates that the helper enterprise bean failed and the transaction has been aborted or marked "rollback only". In general, the client does not know the transaction context within which the called enterprise bean operated. The called bean may have operated in its own transaction context separate from the calling program's transaction context, or it may have operated in the calling program's context.
If the EJB operated in the same transaction context as the calling program, then the bean itself (or its container) may have already marked the transaction for rollback. When the EJB container has marked a transaction for rollback, the client should stop all work on the transaction. Normally, a client using declarative transactions will get an appropriate exception, such as javax.transaction.TransactionRolledbackException. Note that declarative transactions are those transactions where the container manages the transaction details.
A client that is itself an EJB calls the javax.ejb.EJBContext.getRollbackOnly method to determine if its own transaction has been marked for rollback or not.
For bean-managed transactions--those transactions managed explicitly by the client--the client should rollback the transaction by calling the rollback method from the java.transaction.UserTransaction interface.
When a transaction is not marked for rollback, then the client has three options:
When a client receives a checked exception for a transaction not marked for rollback, its safest course is to rollback the transaction. The client does this by either marking the transaction as "rollback only" or, if the client has actually started the transaction, calling the rollback method to actually rollback the transaction.
The client can also throw its own checked exception or re-throw the original exception. By throwing an exception, the client lets other programs further up the transaction chain decide whether or not to abort the transaction. However, in general it is preferable for the code or program closest to the occurrence of the problem to make the decision about saving the transaction.
Lastly, the client can continue with the transaction. The client can evaluate the exception message and decide if invoking the method again with different parameters is likely to succeed. However, you need to keep in mind that retrying a transaction is potentially dangerous. You have no knowledge of nor guarantee that the enterprise bean properly cleaned up its state.
Clients that are calling stateless session beans, on the other hand, can retry the transaction with more confidence if they can determine the problem from the thrown exception. Because the called bean is stateless, the client does not have the problem of not knowing the state in which the bean left the transaction.