| CONTENTS | PREV | NEXT | INDEX | Designing Enterprise Applications with the J2EETM Platform, Second Edition |
Most enterprise information systems support some form of transactions. For example, a typical JDBC database allows multiple SQL updates to be grouped in an atomic transaction.
Components should always access an enterprise information system within the scope of a transaction to guarantee the integrity and consistency of the underlying data. Such systems can be accessed within a JTA transaction or a resource manager local transaction.
When an enterprise information system is accessed within the scope of a JTA transaction, any updates performed on the system will commit or roll back depending on the outcome of the JTA transaction. Multiple connections to information systems can be opened and all updates through the connections will be atomic if they are performed within the scope of a JTA transaction. The J2EE server is responsible for coordinating and propagating transactions between the server and the enterprise information system.
If the J2EE product supports multiple enterprise information systems in one transaction, a J2EE application can access and perform updates on multiple enterprise information systems atomically, without extra programming effort, by grouping all updates within a JTA transaction. Code Example 8.3 illustrates this use:
InitialContext ic = new InitialContext("java:comp/env");
DataSource db1 = (DataSource) ic.lookup("OrdersDB"); // JDBC
ConnectionFactory db2 =
(ConnectionFactory) ic.lookup("InventoryEIS"); // Connector CCI
java.sql.Connection con1 = db1.getConnection();
javax.resource.cci.Connection con2 = db2.getConnection();
UserTransaction ut = ejbContext.getUserTransaction();
ut.begin();
// perform updates to OrdersDB using connection con1
// perform updates to InventoryEIS using connection con2
ut.commit();
| Code Example 8.3 Accessing Multiple Transactional Resources |
A resource manager local transaction (or local transaction) is a transaction specific to a particular enterprise information system connection. A local transaction is managed by the underlying enterprise information system resource manager. The J2EE platform usually does not have control of or knowledge about any local transactions begun by components. Access to a transactional enterprise information system is usually within a local transaction if no JTA transaction has been initiated. For example, if a servlet accesses a JDBC database without starting a JTA transaction, the database access will be within the scope of a local transaction, specific to the database.
Local transactions may also be used when the enterprise information system is not integrated using the Connector architecture. For example, if no Connector resource adapter is available for an object-oriented database, a J2EE server cannot propagate any JTA transactions to the object-oriented database, and any access will be within local transactions. For this reason, applications should use the Connector architecture to integrate enterprise information systems that are not included as part of the J2EE platform.
Enterprise information systems such as databases should be accessed within the scope of a JTA transaction. Transactional access guarantees data consistency and integrity, and ensures that work performed by multiple components through multiple enterprise information system connections is grouped as an atomic unit. It also groups as an atomic unit work performed on one or more independent enterprise information systems.
Where JTA transaction control is not possible, such as with resource managers that do not support the JTA, consider using resource manager local transactions with compensating transactions (see the next section). Keep in mind that each local transaction requires an explicit commit or rollback. In addition, components using local transactions need extra logic to deal with individual enterprise information system rollbacks or failures.
A compensating transaction is a transaction or a group of operations that undoes the effect of a previously committed transaction. A distributed transaction may include both JTA transactions and resource manager local transactions, but local transactions require explicit management. JTA-enabled resource managers handle rollback automatically by simply discarding any changes made since a transaction began. But each resource manager local transaction requires a compensating transaction that can undo the local transactions effects in case a rollback occurs.
Compensating transactions are useful if a component needs to access an enterprise information system that either does not support full JTA transactions or is not supported by a particular J2EE product. The J2EE platform supports JTA transactions for JDBC and JMS access. JTA transaction support for EIS access is determined by the transaction level of the resource adapter (see Section 8.8.3 on page 274). An XATransaction resource adapter automatically supports JTA transactions.
A LocalTransaction resource adapter (see Section 8.8.3 on page 274) accesses an EIS within the scope of a resource manager local transaction. Performing atomic operations on multiple EISs can be challenging when some of those systems do not participate in the JTA transaction. Compensating transactions meet this challenge by providing programmatic "rollback" of operations already committed by resource manager local transactions. Compensating transactions must be manually coded into application logic; the JTA provides no standard way to handle them.
For example, suppose an application needs to perform an atomic operation that involves updating two enterprise information systems: a database that supports JTA transactions and an enterprise resource planning system that does not. The application would need to define a compensating transaction for the update to the enterprise resource planning system. The approach is illustrated in Code Example 8.4.
updateERPSystem();
try {
UserTransaction.begin();
updateJDBCDatabase();
UserTransaction.commit();
}
catch (RollbackException ex) {
undoUpdateERPSystem();
}
| Code Example 8.4 Compensating Transaction |
The methods updateERPSystem and updateJDBCDatabase contain code to access and perform work on enterprise information systems. The undoUpdateERPSystem method contains code to undo the effect of updateERPSystem if the JTA transaction does not commit successfully.
Compensating transactions have a few pitfalls:
8.7.4.1 Compensating Transaction Guidelines
- Committed transactions cannot always be undone--Consider Code Example 8.4. If for some reason the method
undoUpdateERPSystemfails, the data will be left in an inconsistent state.- Server crashes can compromise atomicity--For example, if the system crashes immediately after the method
updateERPSystem, the two database updates will not occur, resulting in a partial transaction- Resource manager local transaction commits can violate isolation--When using compensating transactions with non-JTA resources, committed resource manager local transactions may subsequently be undone. In Code Example 8.4, a concurrent enterprise information system client may be using data from the committed update to the enterprise resource planning system, and this data may potentially be rolled back later. In other words, updates committed by a resource manager local transaction may be visible to other transactions, even before the distributed transaction commits.
Compensating transaction code should be encapsulated in a session enterprise bean with a bean-managed transaction. The session bean may implement all of the enterprise information system access logic itself, or delegate some or all of the access logic to other enterprise beans. If an enterprise bean's only responsibility is to access an enterprise information system that does not support JTA transactions, its transaction attribute should be set to NotSupported to indicate that a JTA transaction will not be used in the enterprise bean.
An application that depends on compensating transactions must have extra logic to deal with potential failures and inconsistencies. The extra work and pitfalls of compensating transactions mean applications should avoid using them when possible. Instead, use JTA transactions to simply and safely achieve ACID transaction properties across multiple components and enterprise information systems.
An isolation level defines how concurrent transactions to an enterprise information system are isolated from one another. Enterprise information systems usually support the following isolation levels:
ReadCommitted--This level prevents a transaction from reading uncommitted changes from other transactions.RepeatableRead--This level prevents a transaction from reading uncommitted changes from other transactions. In addition, it ensures that reading the same data multiple times will return the same value even if another transaction modifies the data.Serializable--This level prevents a transaction from reading uncommitted changes from other transactions and ensures that reading the same data multiple times will return the same value even if another transaction modifies the data. In addition, it ensures that if a query retrieves a result set based on a predicate condition and another transaction inserts data that satisfy the predicate condition, re-execution of the query will return the same result set.
Isolation level and concurrency are closely related. The isolation level indicates the degree of responsibility given to the EIS for managing concurrent data access. A lower isolation level typically allows greater concurrency, at the expense of more complicated logic to deal with potential data inconsistencies. A higher isolation level typically allows simpler logic, at the expense of system performance due to internal EIS data locking to enforce ACID transaction properties. A useful guideline is to use the highest isolation level provided by enterprise information systems that gives acceptable performance.
For consistency, all enterprise information systems accessed by a J2EE application should use the same isolation level. The J2EE specification version 1.3 does not define a standard way to set isolation levels when an enterprise information system is accessed within JTA transactions. If a J2EE product does not provide a way to configure the isolation level, the enterprise information system default isolation level will be used. For most relational databases, the default isolation level is ReadCommitted.
The isolation level should not change within a transaction, especially if some work has already been done. Some enterprise information systems will force a commit if you attempt to change the isolation level.
The J2EE platform provides distributed transaction support across multiple resource managers, including JDBC databases, JMS providers, and EISes. The performance impact of using multiple resource managers in the same transaction is an important concern. Typically, a transaction that accesses more than one resource manager uses the two-phase distributed commit protocol, resulting in additional transaction processing overhead. Distributed transactions also cause additional administrative overhead; for example, partial failures of in-doubt transactions must always be resolved. Therefore, an application should minimize the use of multiple resource managers in the same transaction where possible (for example, by consolidating data into one EIS). However, JTA transactions should definitely be used when accessing multiple transactional resources. The benefits of data integrity and ease of programming that JTA transactions provide definitely outweigh the additional overhead incurred by two-phase commit.