Wouldn't it be great if every operation your application attempts
succeeds? Unfortunately, in the multi-threaded world of
distributed applications and shared resources, this is not
always possible.
Why? First of all, shared resources must maintain a consistent view of
the data to all users. This means reads and writes have to be managed
so users do not overwrite each other's changes, or transaction errors
do not corrupt data integrity. Also, if you factor in intermittent
network delays or dropped connections, the potential for operations to fail
in a web-based application increases as the number of users increases.
If operation failures are unavoidable, the next best thing is to
recover safely, and that is where transaction management
fits in. Modern databases and transaction managers let you undo and
restore the state of a failed sequence of operations to ensure the
data is consistent for access by multiple threads.
This section adds code to SellerBean from the auction house
example so it can manage its auction item insertion transaction beyond the
default transaction management provided by its container.
Why Manage Transactions?
When you access a databases using the JDBCTM application
programming interface (API), all operations are run with an
explicit auto commit by default. This means any other
application viewing this data will see the updated data after
each JDBC call.
For simple applications this may be acceptable, but consider
the auction application and the sequences that occur when
SellerBean inserts an auction item. The user's
account is first charged for listing the item, and the item is
then added to the list of items up for auction. These
operations involve RegistrationBean
to debit the account and AuctionItemBean to add
the item to the auction list.
In auto commit mode, if the auction item insertion fails, only
the listing is backed out, and you have to manually adjust the user's
account to refund the listing charge. In the meantime, another
thread might try to deduct from the same user's account, find no
credit left, and abort when perhaps a few milliseconds later it would
have completed.
There are two ways to ensure the debit is backed out if the auction item
insertion fails:
- Add session synchronization code to a container-managed session
Bean to gain control over transaction commits and roll backs.
- Configure JDBC services to transaction commit mode and add code to start,
stop, commit, and rollback the transaction. This is a
Bean-managed transaction and can be used with an entity or session Bean.
Session Synchronization
A container-managed session Bean can optionally include session
synchronization to manage the default auto commit provided by
the container. Session synchronization code lets the container notify
the Bean when important points in the transaction are reached. Upon
receiving the notification, the Bean can take any needed actions before
the transaction proceeds to the next point.
Note:
A session Bean using
Bean-managed transactions does not need session synchronization
because it is in full control of the commit.
Container-Managed Example
SellerBean is a session Bean that uses
RegistrationBean to check the user ID and password when
someone posts an auction item and debit the seller's account
for a listing, and AuctionItemBean to add new auction
items to the database.
The transacton begins in the insertItem method
with the account debit and ends when the entire
transaction either commits or rolls back. The entire transaction including the
50 cents debit rolls back if the auction item is null
(the insertion failed), or if an exception is caught.
If the auction item is not null
and the insertion succeeds, the entire transaction including the 50
cents debit commits.
Code
To use session synchronization, a session Bean implements the
SessionSynchronzation interface and its three
methods, afterBegin , beforeCompletion ,
and afterCompletion . This example adapts the
SellerBean.java
code to use session synchronization.
public class SellerBean implements SessionBean,
SessionSynchronization {
private transient SessionContext ctx;
private transient Properties p = new Properties();
private transient boolean success = true;
public void afterBegin() {}
public void beforeCompletion() {
if (!success ) {
ctx.setRollbackOnly();
}
}
public void afterCompletion(boolean state) {}
afterBegin : The container calls this
method before the debit to notify the session Bean a new transaction
is about to begin. You can implement this method to do any preliminary
database work that might be needed for the transaction. In this example,
no preliminary database work is needed so this method has no implementation.
beforeCompletion : The container calls
this method when it is ready to write the auction item and debit to
the database, but before it actually does (commits). You can implement
this method to write out any cached database updates or roll back the
transaction. In this example, the method calls the setRollbackOnly
method on its session context in the event the success
variable is set to false during the transaction.
afterCompletion : The container calls
this method when the transaction commits. A boolean value
of true means the data committed and false means
the transaction rolled back. The method uses the boolean value to
determine if it needs to reset the Bean's state in the case of a rollback.
In this example, there is no need to reset the state in the event of
a failure.
Here is the insertItem method with comments showing
where the points where the SessionSynchronization
methods are called.
public int insertItem(String seller,
String password,
String description,
int auctiondays,
double startprice,
String summary)
throws RemoteException {
try{
Context jndiCtx = new InitialContext(p);
RegistrationHome rhome =
(RegistrationHome) sCtx.lookup("registration");
RegistrationPK rpk=new RegistrationPK();
rpk.theuser=seller;
Registration newseller=rhome.findByPrimaryKey(rpk);
if((newseller == null) ||
(!newseller.verifyPassword(password))) {
return(Auction.INVALID_USER);
}
//Call to afterBegin
newseller.adjustAccount(-0.50);
AuctionItemHome home = (AuctionItemHome)
jndiCtx.lookup("auctionitems");
AuctionItem ai= home.create(seller,
description,
auctiondays,
startprice,
summary);
if(ai == null) {
success=false;
return Auction.INVALID_ITEM;
}
else {
return(ai.getId());
}
}catch(Exception e){
System.out.println("insert problem="+e);
success=false;
return Auction.INVALID_ITEM;
}
//Call to beforeCompletion
//Call to afterCompletion
}
Transaction Commit Mode
If you configure the JDBC services to transaction commit mode, you can
have the Bean manage the transaction. To set the JDBC services to commit,
call con.setAutoCommit(false) on your JDBC connection.
Not all JDBC drivers support commit mode, but to have the Bean control and
manage transactions, you need a JDBC driver that does.
Transaction commit mode lets you add code that creates a safety net around a
sequence of dependent operations. The JavaTM Transaction API (JTA) provides
the hooks you need to create that safety net. But, if you are using
the Enterprise JavaBeans architecture, you can do it with a lot
less code. You only have to configure the Enterprise JavaBeans server,
and specify where the transaction starts, stops, rolls back, and commits
in your code.
Server Configuration
Configuring the Enterprise JavaBeans server involves specifying
the following settings in a configuration file for each Bean:
- An isolation level to specify how exclusive a
transaction's access to shared data is.
- A transaction attribute to specify how to handle
Bean-managed or container-managed transactions that continue in another Bean.
- A transaction type to specify whether the transaction is
managed by the container or the Bean.
For example, you would specify these settings for the
BEA Weblogic
server in a DeploymentDescriptor.txt file
for each Bean.
Here is the part of the DeploymentDescriptor.txt
for SellerBean that specifies the isolation level
and transaction attribute. A description of the settings
follows.
(controlDescriptors
(DEFAULT
isolationLevel TRANSACTION_SERIALIZABLE
transactionAttribute REQUIRED
runAsMode CLIENT_IDENTITY
runAsIdentity guest
); end DEFAULT
); end controlDescriptors
Here is the equivalent Enterprise JavaBeans 1.1 extended markup
language (XML) description that specifies the transaction type.
In this example SellerBean is container managed.
<container-transaction>
<method>
<ejb-name>SellerBean<ejb-name>
<method-name>*<method-name>
<method>
<transaction-type>Container<transaction-type>
<trans-attribute>Required<trans-attribute>
<container-transaction>
In this example, SellerBean is Bean managed.
<container-transaction>
<method>
<ejb-name>SellerBean<ejb-name>
<method-name>*<method-name>
<method>
<transaction-type>Bean<transaction-type>
<trans-attribute>Required<trans-attribute>
<container-transaction>
Transaction Attribute Descriptions:
An enterprise Bean uses a transaction attribute
to specify whether a Bean's transactions are managed by
the Bean itself or by the container, and how to handle
transactions that started in another Bean.
The Enterprise JavaBeans server can control only one transaction
at a time. This model follows the example set by the OMG
Object Transaction Service (OTS), and means the current Enterprise
JavaBeans specification does not provide a way to nest transactions.
A nested transaction is a new transaction that starts from within
an existing transaction. While transaction nesting is not allowed,
continuing an existing transaction in another Bean is okay.
When a Bean is entered, the server creates a transaction
context to manage the transaction. When the transaction is
managed by the Bean, you access the context to begin,
commit, and rollback the transaction as needed.
Here are the transaction attributes with a brief description for
each one. The attribute names changed betweein the 1.0 and
1.1 versions of the Enterprise JavaBeans specification.
1.1 Specification |
1.0 Specification |
Container-managed
transaction.
The server either starts and manages a new
transaction on behalf of the user or continues using the transaction that
was started by the code that called this Bean.
|
REQUIRESNEW |
TX_REQUIRED_NEW |
Container-managed transaction.
The server starts and manages a new transaction. If an existing transaction
starts this transaction, it suspends until this transaction completes.
|
Specified as Bean transaction-type in
deployment descriptor |
TX_BEAN_MANAGED |
Bean-managed transaction.
You access the transaction context to begin, commit, or rollback
the transaction as needed.
|
If the code calling this Bean has a transaction running,
include this Bean in that transaction.
|
If the code calling a method in this Bean has a transaction running,
suspend that transaction until the method called in this Bean completes.
No transaction context is created for this Bean.
|
The transaction attribute for this Bean is set when another
Bean calls one of its methods. In this case, this Bean gets
the transaction attribute of the calling Bean. If the calling
Bean has no transaction attribute, the method called in this
Bean throws a TransactionRequired exception.
|
Isolation Level Descriptions:
An enterprise Bean uses an isolation level to
negotiate its own interaction with shared data and
the interaction of other threads with the same shared data.
As the name implies, there are various levels of isolation
with TRANSACTION_SERIALIZABLE providing the
highest level of data integrity.
Note:
Be sure to verify your database can handle the level
you choose. In the Enterprise JavaBeans 1.1 specification,
only Bean-managed persistence session Beans can set the
isolation level.
If the database cannot handle the isolation level, the Enterprise
JavaBeans server will get a failure when it tries to call the
setTransactionIsolation JDBC method.
TRANSACTION_SERIALIZABLE : This
level provides maximum data integrity. The Bean gets what amounts
to exclusive access to the data. No other transaction can read or
write this data until the serializable transaction completes.
Serializable in this context means process as a serial operation,
and should not be confused with serializing objects to preserve and
restore their states. Running transactions as a single serial operation
is the slowest setting. If performance is an issue, use another isolation
level that meets your application requirements, but provides better
performance.
TRANSACTION_REPEATABLE_READ : At
this level, data read by a transaction can be read, but not
modified, by another transaction. The data is guaranteed to have
the same value it had when first read, unless the first transaction
changes it and writes the changed value back.
TRANSACTION_READ_COMMITTED :
At this level, data read by a transaction cannot be read
by other transactions until the
frist transaction either commits or rolls back.
TRANSACTION_READ_UNCOMMITTED :
At this level, data involved in a transaction can be read by
other threads before the first transaction either completes or
rolls back. The other transactions cannot tell if
the data was finally committed or rolled back
Bean-Managed Example
SellerBean is a session Bean that uses
RegistrationBean to check the user ID and password when
someone posts an auction item and debit the seller's account
for a listing, and AuctionItemBean to add new auction
items to the database.
The transacton begins in the insertItem method
with the account debit and ends when the entire
transaction either commits or rolls back. The entire transaction including the
50 cents debit rolls back if the auction item is null
(the insertion failed), or if an exception is caught.
If the auction item is not null
and the insertion succeeds, the entire transaction including the 50
cents debit commits.
For this example, the isolation level is TRANSACTION_SERIALIZABLE ,
and the transaction attribute is TX_BEAN_MANAGED .
The other Beans in the transaction, RegistrationBean
and AuctionItemBean , have an isolation level of
TRANSACTION_SERIALIZABLE and a transaction attribute
of REQUIRED .
Changes to this version of SellerBean over the
container-managed version are flagged with comments.
public int insertItem(String seller,
String password,
String description,
int auctiondays,
double startprice,
String summary)
throws RemoteException {
//Declare transaction context variable using the
//javax.transaction.UserTransaction class
UserTransaction uts= null;
try{
Context ectx = new InitialContext(p);
//Get the transaction context
uts=(UserTransaction)ctx.getUserTransaction();
RegistrationHome rhome = (
RegistrationHome)ectx.lookup("registration");
RegistrationPK rpk=new RegistrationPK();
rpk.theuser=seller;
Registration newseller=
rhome.findByPrimaryKey(rpk);
if((newseller == null)||
(!newseller.verifyPassword(password))) {
return(Auction.INVALID_USER);
}
//Start the transaction
uts.begin();
//Deduct 50 cents from seller's account
newseller.adjustAccount(-0.50);
AuctionItemHome home = (
AuctionItemHome) ectx.lookup("auctionitems");
AuctionItem ai= home.create(seller,
description,
auctiondays,
startprice,
summary);
if(ai == null) {
//Roll transaction back
uts.rollback();
return Auction.INVALID_ITEM;
}
else {
//Commit transaction
uts.commit();
return(ai.getId());
}
}catch(Exception e){
System.out.println("insert problem="+e);
//Roll transaction back if insert fails
uts.rollback();
return Auction.INVALID_ITEM;
}
}
[TOP]
|