By: William
B. Heys,
Senior Consultant, Whittman-Hart, Inc.
This article
originally appeared in PowerTimes
magazine.
Click
here to download the pdf version of this document.
Introduction
Transaction
management is one of the most important elements of good application architecture
and design. In this article, I will describe transaction management with Enterprise
Application Server™ (EAServer) and show how this will affect your component
and application design.
Transaction
Management Topics
-
Introducing
Transactions
-
Transaction
Management in Traditional Client/Server Applications
-
Transaction
Management in Jaguar™
-
Component
Transaction Properties
-
Transaction
Server Object
-
Transaction
State Primitives
-
Stateful
vs. Stateless Components
-
Component
Deactivation
-
Connection
Management
-
Transaction
Coordinators
-
Component
Design
-
Summary
Introducing Transactions
A transaction
is comprised of one or more SQL statements grouped together into an atomic unit
of work that must succeed or fail as a whole. By grouping multiple SQL requests
into a single unit of work, it is possible to ensure database consistency. Proper
transaction management ensures that all databases updated by an application
are always left in a consistent state.
Databases use
transactions to implement isolation. All changes made by one user within a transaction
should be isolated from changes made by other users running simultaneously.
Proper transaction
design and management is necessary to maximize the concurrency of an application.
Without proper transaction management, updates made by one user may cause long-duration
locks to be placed on large numbers of rows in the database. Until these locks
are released, other users will be unable to access or update these rows, and
may even be put into extended wait periods. To increase concurrency, one must
minimize the scope and duration of transactions, while at the same time being
careful to maintain database consistency.
-
Introduction
& Introducing Transactions
-
Transaction
Management in Traditional Client/Server Applications & Transaction Management
in Jaguar
-
Component
Transaction Properties & Transaction Server Object
-
Transaction
State Primitives & Stateful vs. Stateless Components
-
Component
Deactivation & Connection Management
-
Transaction
Coodination & Component Design
-
Summary
& About the Author
Transaction Management
in Traditional Client/Server Applications
In traditional
client/server two-tier applications, transaction management is the responsibility
of the client application. When an application connects to the database, a new
database transaction is started. The current transaction ends and a new transaction
begins when the application sends a COMMIT or ROLLBACK request to the database.
To support
transaction management, a database uses a combination of logging and locking.
All changes made to a database are written to a log file. When a row is deleted,
an image of the deleted row is written to the log. When a row is changed, an
image of the row before changes are applied is written to the log. When a new
row is inserted, the primary key of the new row is written to the log.
All database
updates made by an application remain in a pending state until the outcome of
the transaction is known. To ensure proper isolation, the database places locks
on updated rows (or pages). These locks prevent other applications from reading
or updating a row while it is in an update pending state.
When a transaction
is committed, all database changes made by the application since the start of
the transaction are made permanent, and all update locks are released. The current
transaction ends and a new transaction begins.
When a transaction
is rolled back, all database changes made since the start of the transaction
are removed from the database. Using the log, the database is returned to its
state when the transaction was started, and all update locks are released. The
current transaction ends and a new transaction begins.
Back
to article outline.
Transaction Management
in Jaguar
Responsibility
for transaction management in Jaguar shifts from the client application to the
Jaguar Transaction Manager. Jaguar changes how transactions are managed and
controlled. Rather than being scripted, transaction management becomes a deployment-time
component property. The Jaguar Transaction Manager allows you to group database
updates performed by multiple components into a single atomic unit of work.
Components
utilizing Jaguar's transaction management participate in implicit transactions.
Rather than directly controlling the outcome of a transaction using COMMIT or
ROLLBACK database commands, a component votes on the outcome of a transaction
by invoking transaction state primitives.
If a component
does not participate in implicit transactions, it can explicitly manage its
own transaction by issuing COMMIT and ROLLBACK database commands.
Back
to article outline.
Component
Transaction Properties
Jaguar components
have a transaction support property governing how the component participates
in Jaguar transactions. The transaction support property has one of the following
values:
Transactions
currently running within an implicit Jaguar-managed transaction are characterized
as transactional components. Components that are not currently running within
an implicit transaction are non-transactional.
When a component
specifies a transaction is not supported, it never runs in an implicit transaction
managed by Jaguar, and must manage its own transactions explicitly.
A component
that supports a transaction may or may not participate in an implicit transaction.
If the component is invoked by a transactional component, it will participate
in the invoking component's transaction. If the component is invoked directly
by the client or by another non-transactional component, it will not be part
of a transaction.
A component
that requires a transaction will always participate in an implicit transaction.
If the component is invoked by a transactional component, it will participate
in the invoking component's transaction. If the component is invoked directly
by the client or by a non-transactional component, it will start a new transaction.
When a component
requires a new transaction, it will always start a new transaction regardless
of how it is invoked. If the component is invoked by another transactional component,
the invoking component's transaction will be separate from the invoked component's
transaction.
Back
to article outline.
TransactionServer Object
PowerBuilder
provides a special transaction service context object called TransactionServer.
To use Jaguar's implicit transaction management, components must create a TransactionServer
object.
First declare
an instance variable of type TransactionServer:
Protected:
TransactionServer its_transactionserver
Next, create
the TransactionServer object in the activate event of the component using
the GetContextService function:
this.GetContextService
&
"TransactionServer", &
its_transactionserver)
The TransactionServer
object should be destroyed in the component's deactivate event.
Back
to article outline.
Transaction State Primitives
Components
participate in implicit transaction management by invoking transaction state
primitives, which are methods of the TransactionServer object. These primitives
are:
-
CompleteWork
-
RollbackWork
-
ContinueWork
-
DisableCommit
-
InTransaction
-
IsRollbackOnly
Each of these
transaction primitives has corresponding methods of the TransactionServer object.
In PowerBuilder, these methods are:
-
SetComplete
-
SetAbort
-
EnableCommit
-
DisableCommit
-
IsInTransaction
-
IsTransactionAborted
Transaction
state primitives serve two functions. First, they are used are used by a component
in place of SQL COMMIT and ROLLBACK requests to vote on the success or failure
of the transaction. Second, they indicate whether the component should be deactivated
or remain bound to the client.
The first two
state primitives, CompleteWork (SetComplete) and RollbackWork (SetAbort),
vote on the success or failure of the transaction and cause the component to
be deactivated.
The second
two state primitives, ContinueWork (EnableCommit) and DisableCommit (DisableCommit)
are only used by a component to vote on the success or failure of the transaction
but do not cause the component to be deactivated.
The last two
state primitives allow a component to query the state of its transaction. To
determine whether the current method is running within a transaction, it calls
the InTransaction primitive (IsInTransaction). To determine whether the
transaction has already been aborted, it invokes the IsRollbackOnly primitive
(IsTransactionAborted).
Individual
components can only vote for the success or failure of a transaction. The transaction
does not end until the root component of the transaction is deactivated. At
that time, the votes issued by participating components are counted. If any
no vote is received, the transaction is aborted and databases changes are rolled
back. If only yes votes are received, the transaction succeeds and database
changes are committed.
Back
to article outline.
Stateful vs. Stateless
Components
A stateful
component maintains information about a client across method invocations. Typically
stateful components remain bound to a client and store state information in
properties or instance variables. While bound to a particular client, a stateful
component cannot be shared or used by any other client.
Stateless components
are deactivated and unbound from a client following each method call. Because
stateless components do not store information in properties or instance variables
across method invocations, they can be released reused by other clients more
quickly.
There are many
disadvantages to stateful components. Stateful components have longer lifetimes
and require more resources than stateless components. More instances of a stateful
component must be created to support a given number of clients.
Stateful components
also tend to cause an increase of network traffic. A client may invoke several
methods on the same component instance to change or retrieve information saved
in its instance variables. For example, with a stateful component, a client
may call one method to retrieve data from the database into a data store in
the component. Without releasing the component, the client will subsequently
invoke additional methods to bring this data from the data store back to the
client. In order to make the component stateless, the data must be retrieved
and returned in a single method invocation.
Applications
built with stateless components will be more scalable than applications built
with stateful components. Stateless components are better able to take advantage
of server clusters, load balancing, and the automatic failover features of Jaguar.
Back
to article outline.
Component Deactivation
Stateless components
can support early deactivation. Such components are automatically deactivated
when a method returns. To enable early deactivation a component must be stateless
and must set the deployment-time property for automatic demarcation/deactivation
to true.
A stateful
component must tell Jaguar when it can be deactivated. If the component participated
in implicit transactions, the component invokes transaction primitives SetComplete
or SetAbort to cause deactivation. If a component must remain bound to a client,
it alternatively invokes the transaction primitives EnableCommit or DisableCommit.
Stateless components are also deactivated when a method returns after invoking
the SetComplete or SetAbort methods.
Back
to article outline.
Connection Management
Jaguar can
maintain connection caches which are pools of database connections. Rather than
physically connect and disconnect from a database, a component can request a
connection from the connection manager. If a connection is available in the
pool, it will be reused. Otherwise, a new physical connection is created.
When such a
connection is released by a component, it is returned to the pool, but not physically
disconnected from the database. The connection is now available for reuse.
Connection
caching improves performance by reducing the total number of physical database
connections. Connection caching is also necessary if you want Jaguar to manage
transactions.
Back
to article outline.
Transaction Coordinators
Jaguar supports
two transaction coordinators. The default transaction coordinator is Jaguar's
Shared Connection coordinator. With the Shared Connection coordinator, a component
must use a database connection cache. By using a connection cache, Jaguar ensures
all components participating in the same implicit transaction also use the same
physical database connection.
Jaguar also
supports Microsoft's Distributed Transaction Coordinator (DTC). The DTC supports
transactions spanning multiple database connections using two-phase commit.
In order to use DTC, you must be using Windows NT, ODBC, and a DTC-compliant
database. At the current time, only Microsoft SQL Server is DTC-compliant.
In the future,
Jaguar will support additional transaction coordinators. The Java Transaction
Server (JTS) and the CORBA Object Transaction Server (OTS) will be supported.
In addition, Jaguar will support Transarc Encina's two-phase commit process.
Back
to article outline.
Component
Design
When illustrating
transaction management, almost everyone seems to use the ATM example. In this
example, a customer wishes to transfer some money from one account to another.
Let's say the customer wants to transfer $100.00 from his checking account to
his savings account. The application probably needs a method to withdraw money
from one account and another method to deposit money to the second account.
A third method would create an audit trail of the transfer transaction.
An initial
design might involve two classes. The first would be an account class with two
methods, Withdraw () and Deposit (). The second would be a log class with one
method, Log ().
If this was
a two-tier client/server application, the application might instantiate two
instances of the account class (one for the checking account and one for the
savings account) and one instance of the log class. Pseudo code for this transaction
might look something like this:
Integer ll_rc
Decimal ld_amount
Connect using SQLCA;
ld_amount = 100.00
li_rc = AccountFrom.Withdraw (ld_amount)
IF li_rc > 0 THEN
li_rc = AccountTo. Deposit (ld_amount)
END IF
IF li_rc > 0 THEN
li_rc = Log.Log (AccountFrom.Nbr, &
AccountTo.Nbr,
ld_amount)
END IF
IF li_rc > 0 THEN
Commit Using SQLCA;
ELSE
Rollback Using SQLCA;
END IF
Disconnect Using SQLCA;
Next, we deploy
these components to Jaguar. Since we want one transaction to encompass all three
methods, Withdraw (), Deposit (), and Log (), we deploy both components with
transaction support. Start by setting both components to Requires Transaction.
What happens when the client application instantiates two account components
and one log component? Each will start and run in its own transaction. Whenever
a component is instantiated by the client, it cannot participate in the same
transaction with other components instantiated by the client.
To solve this
problem, create a third component for the customer. This component would have
one method, Transfer. The customer component should be deployed to Jaguar with
the Requires Transaction setting.
The client
application instantiates only the customer component. The customer component
becomes the root instance of a single Jaguar transaction. The customer component
becomes like a container object and instantiates the two account components
and the log component. Since the customer component is running in a transaction
when it creates the account and log components, they run within the customer's
transaction.
The client
application calls the customer component's Transfer method, passing the transfer
amount, and two account numbers. The transfer method, in turn, calls the Withdraw
method for the first account and the Deposit method for the second account and
finally the Log method for the log component. All three method invocations will
be part of a single atomic transaction.
A benefit of
implementing the Log method in its own component is flexibility in the design
of the transaction. To separate the Log method from the transaction involving
the Withdraw and Deposit methods, simply change the log component's transaction
support from Requires Transaction to Requires New Transaction. The Withdraw
and Deposit methods remain as part of one transaction while the log method becomes
a second separate transaction.
Back
to article outline.
Summary
With Enterprise
Application Server components, transaction management is very different from
traditional client/server applications. Because of these differences, you will
want to design your components differently when you deploy them to Jaguar. Since
transaction support is now set at the component level with a deployment-time
property, rather than controlled in scripts, you will probably want to design
smaller, simpler, more granular components.
Most of us
probably were not aware of the benefits of stateless components when we designed
business objects for two-tier client/server applications or Distributed PowerBuilder.
Designs incorporating stateless components and connection caches can take advantage
of instance pooling, early deactivation, and implicit transaction management
resulting in more flexible, scaleable, higher performing applications.
About
the Author
Bill Heys (CPD,
CPI) is a Senior Consultant at Whittman-Hart, Inc. in Lexington, MA. Bill spoke
at the Sybase TechWave conference in Orlando and will be speaking at the Swiss
Sybase and PowerBuilder User Group meeting in Zurich on October 21. You may
write Bill at bheys@whittman-hart.com.