Helping ordinary people create extraordinary websites!
GET OUR NEWSLETTER
Your Email:
 

Understanding JCA Transactions

By Mikhail Genkin
2005-04-22


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.

Tutorial Pages:
» Learn How the Various Levels of Transaction Support Provided by Different EISs and Resource Adapters Can Affect Application Design.
» Overview of JCA Transactions
» Transaction Support Levels
» JCA Transaction Support
» Transaction Demarcation Strategies
» Programmatic Transaction Demarcation
» EJB Deployment Descriptor Settings
» In Conclusion
» Resources


First published by IBM DeveloperWorks


 | Bookmark
Related Tutorials:
» All about JAXP, Part 1
» Make Database Queries Without the Database
» Load List Values for Improved Efficiency
» 2 Ways To Implement Session Tracking
» A Simple Way to Read an XML File in Java
» Develop Aspect-Oriented Java Applications with Eclipse and AJDT

Advertise with Us!


Tutorials Scripts Web Hosting Developer Manuals
Resources