The world of e-business is a rapidly changing environment. Businesses need to integrate business logic and data that are available on existing enterprise information systems (EIS) -- such as Customer Information Control System (CICS), Information Management System (IMS), or SAP -- into new applications. Frequently, vital business transactions are written in procedural languages such as Cobol or C. The J2EE™ platform includes a specification that provides developers with a standard interface for accessing EIS transactions and data: the J2EE Connector Architecture (JCA) specification.
In this article, I'll explain how the JCA transaction contract helps implement transactional behavior in e-business applications. Specifically, you'll learn about the JCA's two transaction demarcation techniques, distributed transaction demarcation and programmatic transaction demarcation. I'll explain the pros and cons of each and offer tips for deciding which one is best suited to your application development scenario. Then I'll walk you through an enterprise programming example implementing programmatic transaction demarcation. The article will conclude with tips for choosing and applying the correct EJB deployment descriptor settings for your transaction demarcation solution.
Techniques described in this article can be implemented with any application server that is compliant with J2EE 1.3 or higher, as well as both JCA 1.0- and JCA 1.5-compliant resource adapters.
A working example will help explore some common JCA transaction issues. In this example, the task is to build an e-commerce application for a company whose primary business is selling manufactured goods to consumers. The company has decided to expand by establishing a Web presence and retailing its goods to a wider audience. The Web site will let any customer with a Web browser access the company's home page; browse the catalog of available products; view detailed price information, availability, and a description of available items; add items to a shopping cart; and, finally, make a purchase. To illustrate the required transactional behavior, I'll focus on the use case where the customer decides to make a purchase. Figure 1 shows the application design.
Figure 1. Accessing EIS in your e-commerce application
The company's existing IT infrastructure is built around two enterprise information systems. EIS1 is a mainframe running Cobol transactions under CICS. Transactions running on this system implement business logic and access data necessary for order entry and customer relationship management (CRM). EIS2 is an IMS system containing the product description catalog, pricing information, and inventory control. To support the required functions, your J2EE application must be able to access and seamlessly integrate data from both systems. In order to make a purchase, you need to perform the following actions as a unit of work (for a single transaction):
- Get current product price (EIS2)
- Enter the customer's order into the order system (EIS1)
- Bill the customer (EIS1)
- Update product availability information (EIS2)
A failure during steps 2, 3, or 4 will result in the undoing of all previous steps.
In this example Web application, the client (a JSP page in the Web tier) holds a reference to, and calls methods on, an instance of the CustomerSession stateful session EJB component. You use CustomerSession to store the shopping cart contents, product information from the catalog, and customer information. During the course of interaction with the end user, as shown in Figure 1, this session bean accumulates and stores product selections and customer-specific information necessary to make a purchase. Methods defined in CustomerSession make calls to methods on stateless session EJB components (with the bean serving as a facade) such as OrderService, to invoke transactions on an EIS.
In this design, the OrderService stateless session bean acts as a facade for EIS1. It defines the following methods:
OrderInfo addOrder(String customerId, ItemInfo itemInfo)uses the JCA common client interface (CCI) to invoke theSHIPTOtransaction on EIS1. This transaction looks up the ship-to address for the customer and prepares information that the EIS then communicates to the shipping department. The return structureOrderInfocontains the order number (which is used for tracking), the shipping costs, and shipping address information.BillingInfo billCustomer(String customerId, OrderInfo orderInfo)also uses the JCA CCI to invoke theBILLTOtransaction on EIS1. This transaction looks up the customer's credit card number and debits the customer's account for the order amount. The return structureBillingInfocontains the total cost to the customer, including shipping fees and structures, the ID number for the order (used for tracking and cancellations), and customer information.void cancelOrder(OrderInfo orderInfo)uses the JCA CCI to invoke theRMVORDtransaction running on EIS1 to cancel the order.
The CatalogService session bean (see Figure 1) acts as a facade to EIS2. It defines the following methods:
double getItemPrice(String itemId)uses the JCA CCI to invoke theITMPRICEtransaction on EIS2. This transaction returns the latest pricing information for the item.void updateStockInfo(String itemId, int numItems)uses the JCA CCI to invoke theUPDSTOCKtransaction on EIS2. This transaction updates the current in-stock information based on item ID. The input parameternumItemscan be positive or negative.
All five of these methods and the underlying EIS transactions running on two different systems have to execute as part of a single business transaction. In the next section, you'll see how this is accomplished.
Different EISs have different transactional behavior. Some EISs do not support transactions at all. Some support transactions but do not support the two-phase commit (2PC) protocol. These are said to support local transactions. Some EISs support local transactions and 2PC. These are said to support distributed, or global transactions. Global transactions are also often called XA transactions because they involve the XAResource interface.
Listing 1 shows a snippet from ra.xml for the CICSECI resource
adapter that you'll use to access EIS1. The LocalTransaction value for the <transaction-support>
element indicates that this resource adapter supports local transactions
but cannot participate in global transactions. The OrderService session bean uses this resource
adapter to access CRM transactions that contain EIS1.
Listing 1. Snippet of the ra.xml descriptor for CICSECI resource adapter
<!DOCTYPE connector PUBLIC "-//Sun Microsystems, Inc.//DTD Connector 1.0//EN"
"http://java.sun.com/dtd/connector_1_0.dtd">
<connector>
<display-name>ECIResourceAdapter</display-name>
<description>CICS J2EE ECI Resource Adapter</description>
<vendor-name>IBM</vendor-name>
<spec-version>1.0</spec-version>
<eis-type>CICS</eis-type>
<version>5.0.0 </version>
<license>
<description> </description>
<license-required>true</license-required>
</license>
<resourceadapter>
<managedconnectionfactory-class>
com.ibm.connector2.cics.ECIManagedConnectionFactory</managedconnectionfactory-class>
<connectionfactory-interface>
javax.resource.cci.ConnectionFactory</connectionfactory-interface>
<connectionfactory-impl-class>
com.ibm.connector2.cics.ECIConnectionFactory</connectionfactory-impl-class>
<connection-interface>javax.resource.cci.Connection</connection-interface>
<connection-impl-class>com.ibm.connector2.cics.ECIConnection</connection-impl-class>
<transaction-support>LocalTransaction</transaction-support>
|
Listing 2 shows a snippet from ra.xml for the IMS resource adapter that I use to access EIS 2. In this case the XATransaction value for the <transaction-support> element indicates that the resource adapter supports global, or distributed transactions.
Listing 2. Snippet of the ra.xml descriptor for IMS resource adapter
<!DOCTYPE connector PUBLIC "-//Sun Microsystems, Inc.//DTD Connector 1.0//EN"
"http://java.sun.com/dtd/connector_1_0.dtd">
<connector>
<display-name>IMS Connector for Java</display-name>
<description>J2EE Connector Architecture resource adapter for IMS
accessing IMS transactions using IMS Connect </description>
<vendor-name>IBM Corporation</vendor-name>
<spec-version>1.0</spec-version>
<eis-type>IMS</eis-type>
<version>1.2.6</version>
<license>
<description>IMS Connector for Java is a component of the IMS Connect product and as such
is not separately licensed. It requires an IMS Connect license.</description>
<license-required>true</license-required>
</license>
<resourceadapter>
<managedconnectionfactory-class>
com.ibm.connector2.ims.ico.IMSManagedConnectionFactory</managedconnectionfactory-class>
<connectionfactory-interface>
javax.resource.cci.ConnectionFactory</connectionfactory-interface>
<connectionfactory-impl-class>
com.ibm.connector2.ims.ico.IMSConnectionFactory</connectionfactory-impl-class>
<connection-interface>
javax.resource.cci.Connection</connection-interface>
<connection-impl-class>
com.ibm.connector2.ims.ico.IMSConnection</connection-impl-class>
<transaction-support>XATransaction</transaction-support>
<config-property>
|
The two resource adapters shown in Listings 1 and 2
differ in their levels of transactional support. In my e-commerce
application, however, I need to coordinate a transaction across these
two different systems. To help manage this, the JCA transaction
contract defines a set of architected interfaces through which the
application server and the EIS coordinate the transaction. The EJB
container talks to the EIS via the resource adapter's implementation of
the ManagedConnection interface, which
represents the physical connection to that EIS. The ManagedConnection implementation typically uses a
proprietary library to talk to the EIS using a protocol it understands.
If the resource adapter supports local transactions, it also
implements the javax.resource.spi.LocalTransaction interface. When
the EJB container needs to initiate the transaction it will call the
getLocalConnection() method on the ManagedConnection implementation instance.
Subsequently it will call the LocalTransactions
begin(), commit(), and rollback() methods to control the transaction.
If the resource adapter supports XA or global transactions it also
implements the javax.transaction.xa.XAResource interface. Every
J2EE-compliant application server has an internal component called the
Transaction Manager. The Transaction Manager is actually an
implementation of the javax.transaction.TransactionManager interface.
This component helps the application server manage transaction
boundaries. The Transaction Manager uses the getXAResource() method defined by the ManagedConnection interface to retrieve an instance
of XAResource. The Transaction Manager uses
methods defined on the XAResource interface
to coordinate the 2PC protocol across multiple resource managers.
Transaction demarcation strategies
The JCA gives you two options for handling transactions: programmatic transaction demarcation or declarative transaction demarcation. The first option requires that you use the Java Transaction API (JTA) to write explicit code for each transaction's begin, commit, and
rollback operations. In this case, your transaction demarcation code is intermixed with code that implements the business logic.
The second approach, declarative transaction demarcation, does not involve any extra coding. If I choose this approach, the EJB deployer will configure the transactional behavior for me by modifying deployment descriptor settings for the beans. The EJB container will then use these deployment descriptor settings to automatically begin, commit, or rollback transactions at specified points. In this case the business logic implemented in the EJB component remains portable and the transactional behavior can be adjusted without re-writing the bean implementation.
For most cases declarative transaction demarcation is the preferred option. Programmatic demarcation is typically used only in cases where declarative transaction demarcation isn't flexible enough. In the case of my example, the two EISs and their corresponding resource adapters have different transaction support levels. Were these the only factors under consideration, a distributed transaction using the 2PC protocol would be the best approach to guarantee data consistency and integrity.
Making things more interesting, however, is the fact that in my example only EIS2 supports distributed transactions. In order to use 2PC, all systems involved in the transaction have to support it. Therefore, I cannot use a distributed transaction, and will have to rely on local transactions in order to guarantee consistent updates across the two systems. I will need to group access to each EIS, and manually undo changes to one EIS if the other one fails. Transactions that undo previously committed changes are frequently called compensating transactions. I'll use programmatic transaction demarcation to provide for greater flexibility for manual updates.
Programmatic transaction demarcation
Listing 3 shows the implementation of the application's placeOrder method defined by the CustomerSession session bean. The method
implementation uses the JTA API to start local transactions that control
access to EIS1 and EIS2. Within the placeOrder method you first access EIS2 to get the
most up-to-date price information. This access, though read-only in
nature, should happen within the scope of a transaction. This is due to
the fact that updates to EIS2 pricing information could be in progress
via other applications, and you need to make sure that you are seeing
consistent, committed pricing data. Note that exception handling and
business logic have been simplified for the sake of brevity.
Listing 3. The placeOrder() method of the CustomerSession EJB (withsimplified error handling for brevity)
public OrderInfo placeOrder(ItemInfo itemInfo, CustomerInfo custInfo)
throws OrderException {
// Get a reference to the UserTransaction.
// Initialize variables.
BillingInfo billingInfo = null;
OrderInfo orderInfo = null;
double itemPrice = 0.0;
UserTransaction ut = null;
try
{
InitialContext ic = new InitialContext();
ut = (UserTransaction)ic.lookup("jta/UserTransaction");
}
catch (Exception e)
{
throw new OrderException(e.getMessage());
}
// Look up latest pricing information in EIS2 including customer discount.
try
{
ut.begin();
itemPrice = catalogService.getItemPrice(itemInfo.getItemId(),
custInfo.getCustomerId());
ut.commit();
}
catch ( Exception e)
{
try
{
ut.rollback();
}
catch (Exception ex)
{
// Rollback failed.
// Log the error here, nothing to recover.
}
// Throw exception back to the UI tier, nothing to compensate yet.
throw new OrderException(e.getMessage());
}
itemInfo.setItemPrice(itemPrice);
// Update EIS1 - local transaction
try
{
ut.begin();
billingInfo = orderService.billCustomer( custInfo.getId(), itemInfo );
orderInfo = orderService.addOrder( custInfo.getId(), itemInfo );
ut.commit();
}
catch ( Exception e)
{
// Nothing to compensate in EIS2 yet.
try
{
ut.rollback();
}
catch (Exception ex)
{
// Rollback failed -- log the error.
// Additional checks and error handling to ensure consistency in EIS1.
}
throw new OrderException(e.getMessage());
}
// Update EIS2.
try
{
ut.begin();
catalogService.updateStockInfo(orderInfo.getItemId(), orderInfo.getItemNumber());
ut.commit();
}
catch( Exception e )
{
// Roll back the original transaction to EIS2.
try
{
ut.rollback();
}
catch (Exception ex)
{
// Rollback failed - log the error.
// Additional checks and error handling.
// Do not exit the method yet.
}
// Compensate changes to EIS1 as a single one-phase transaction.
try
{
ut.begin();
orderService.cancelOrder( orderInfo );
orderService.cancelCharge( billingInfo );
ut.commit();
}
catch ( Exception ex)
{
// Compensation failed, log error
try
{
ut.rollback();
}
catch (Exception exx)
{
// Rollback failed
// Log error
}
throw new OrderException(ex.getMessage());
}
// Throw exception back to the UI tier
throw new OrderException(e.getMessage());
}
return orderInfo;
}
|
The next step is to perform two updates to EIS1: one to the order system and one to the billing system. You can perform these updates within the scope of a single local transaction. If one of the updates fails, you can re-throw the exception to the UI tier and exit the method without attempting to update EIS2. The UI tier will need to notify the user of transaction failure, and solicit input on how to handle the situation. The user may choose to re-try or exit the application. Because both of these operations are running in the same transaction and the previous access to EIS2 is read-only no compensating transactions are required at this stage.
The last thing you'll do is update stock availability on EIS2. You perform this again within the scope of a local transaction; however, this is a different transaction from the already-completed EIS1 transaction. Because you're using programmatic transaction demarcation a failure to update EIS2 will result in the need to undo committed changes to EIS1. This is why the catch block contains calls to EIS1 transactions that cancel updates to the order and billing systems.
It is important to note that the programmatic transaction demarcation approach is not a perfect substitute for distributed transactions with 2PC. If the compensating transactions themselves fail, the systems will be left in an inconsistent state. In a real-world application, additional exception handling would be needed to handle this sort of situation. For example, in the case of compensating transaction failure you could send notification to the system administrator, or use messaging technology to retry these transactions at a later time.
EJB deployment descriptor settings
In an EJB-based solution, the EJB deployer must tell the application server how transaction demarcation will be handled by configuring EJB deployment descriptor settings. The first step in choosing the correct deployment descriptor setting for your application is to asses its requirements. Here's what you know about the example JCA implementation:
- The deployment descriptor for the
CustomerSessionEJB component has to indicate that you're using programmatic (that is, bean-managed) transaction demarcation (TX_BEAN_MANAGED). - Deployment descriptors for
OrderServiceandCatalogServicesession beans should use declarative (that is, container-managed demarcation) and methods on these beans should always be executed in the context of a transaction. - The
OrderServiceandCatalogServicesession beans provide access to underlying EIS transactions. Methods on these beans are called by theCustomerSessionsession bean, which implements the process logic behind your application, as well as by other process-oriented components. - The
CustomerSessionbean should start a transaction prior to calling methods onOrderServicebecause the transaction will encompass multiple operations on EIS1. Because each of the two EIS2 operations can execute as independent transactions (and neither can be part of a global transaction encompassing all four operations), either programmatic or declarative demarcation could be used for the calls toCatalogServicemethods. (Note that I've already opted to let theCustomerSessionbean control the transaction boundaries for all the operations.)
Given these requirements, you could use the TX_REQUIRED deployment descriptor setting for CatalogService and OrderService beans. This setting would ensure that
bean methods could join a transaction already in progress or start a new
transaction if need be. Note that with TX_REQUIRED, if the calling code does not start the
transaction, then each call, including those to EIS1, will take place as
separate transactions -- which is not what you want.
A alternative approach would be to force any component (for example, the
CustomerSession bean) that implements process
logic to perform transaction demarcation. This approach, which could be
accomplished using TX_MANDATORY deployment
descriptor setting for the OrderService and
the CatalogService session beans, would
ensure the desired transactional behavior for the application. If you
chose this approach, the calling component would need to start a
transaction prior to calling methods on the EIS facade, or an exception
would be raised. One disadvantage of this approach is that it requires
components that need only single-method transactions to be responsible
for controlling transaction boundaries.
Specifying the transaction isolation level
The J2EE specification defines a deployment descriptor attribute that
specifies the transaction isolation level for a given EJB method. For
example, the EJB deployer could configure the transaction isolation
level for the getItemPrice() method of the
CatalogService session bean to execute with
the TRANSACTION_READ_COMMITTED isolation
level to ensure that only committed pricing data are read in during the
transaction. By changing the isolation level, the J2EE developer or
deployer can balance data integrity against performance constraints to
performance tune the application.
However, different EISs differ in their transactional capabilities and will react to transaction isolation settings in different ways. In this case, changing transaction isolation settings for EIS facades will make little difference, as the ECI and XCF communication protocols will not communicate this information to the back ends. COBOL transactions running on these systems will address transactional isolation aspects using programmatic techniques specific to those platforms.
In general, to determine how a given target EIS will respond to transaction isolation settings you will need to consult documentation for that EIS, and for the resource adapter that is used to provide connectivity. Although resource adapters used in the example do not respond to changes in J2EE transactional isolation level settings, some EISs built around relational database systems may be affected. It is important to understand the exact behavior that will result due to these changes in a particular EIS, as this can have significant impact on performance characteristics of the EIS and your application. In a worst-case scenario, the integrity of the underlying data could be compromised. Developers working on J2EE projects should consult with EIS administrators to ensure that correct policies for transactional isolation are being followed.
The Java Connector Architecture specification provides J2EE developers with a convenient solution for building transactional J2EE applications that involve legacy systems. JCA allows you to integrate existing EIS while maintaining correct transactional semantics required for e-business.
The key problem you will typically need to address has to do with the fact that different resource adapters provide different levels of transaction support. In many cases it may not be possible to rely on the underlying transaction manager to coordinate a distributed transaction among affected systems. In some cases, although possible, distributed transactions may not be desirable due to the performance overhead associated with the two phase commit protocol.
In these cases you may need to rely on compensating transaction logic and programmatic transaction demarcation. This approach also has its drawbacks. Compensating transactions may themselves fail, leaving the system in an inconsistent state.
Another pitfall to watch for has to do with the fact that the JCA specification does not describe how the resource adapter should handle EJB transaction isolation levels. Some resource adapters simply ignore these settings due to the fact that the target EIS-specific communication protocol does not propagate this information to the back end, or due to the fact that the target EIS transaction model does not provide any equivalent notion. In other cases, changes to J2EE transaction isolation levels may dramatically impact EIS performance. To determine the exact behavior you should carefully study resource adapter documentation, and contact the manufacturer if this aspect is not described in sufficient detail.
- Willy Farrell's "Introduction to the J2EE Connector Architecture" tutorial covers the basics of working with JCA.
- "Developing applications with JCA-based tools" (developerWorks, January 2002) introduces the practical aspects of the J2EE Connector Architecture by using JCA-based tools from IBM to build, test, deploy, and run a J2EE EJB application. (Excerpted from J2EE Connector Architecture and Enterprise Application Integration by Rahul Sharma, Beth Stearns, and Tony Ng; Addison-Wesley, 2001.)
- "Build JCA-compliant resource adapters with WebSphere Studio Application Developer" (developerWorks, August 2003) shows you how to write your own JCA-compliant resource adapter.
- "Getting old folks and whippersnappers together" (developerWorks, June
2002) is a guide to integrating legacy CICS transactions using IBM's
JCA-based tools.
- The J2EE
specification is the definitive reference for the EJB programming
model.
- "Getting started with EJB technology" (developerWorks, April 2003) is a comprehensive tutorial introducing the basics of EJB programming and the J2EE environment.
- See the JCA
home page to learn more about the JCA specification.
- You'll find articles about every aspect of Java programming in
the developerWorks Java technology
zone.
- Browse for books on these and other technical topics.
- Also see the Java technology zone tutorials page for a complete listing of free Java-focused tutorials from developerWorks.

Mikhail Genkin is a solution architect working with IBM Integrated Services and Support for WebSphere. He helps IBM customers build business integration solutions. He has contributed to several releases of Visual Age for Java, Enterprise Edition, WebSphere Application Server, Enterprise Edition, and WebSphere Application Developer, Integration Edition.





