Andrew Romanenco.com

Transactions with Spring

 

Scope

This article describes ways of transaction management with spring framework. There are two types of transactions: local transaction and distributed ones. For both types you can use two strategies: program and declarative management.

Introduction

Transaction is set of steps to be proceeded as single unit of job, either all finish with success, or all fail. Imagine banking system and your application must transfer money from one account to another. It actually makes two operations: reduces account A and increases account B. It is extremely important to make both steps together: if account A is reduced, account B must be increased; and if increasing of account B fails, account A must be restored to its original state.
There are two types of transactions: made against single system – local transaction, and against multiple sources – distributed one.

Local transactions are simple: for example you have database connections, you can start transaction, make all steps (transfer money) and commit it.

Distributed transactions control more then one source: the same example – money transfer, but account A is in one database and account B is in another. But you still have to join both operations...here distributed transactions play. Distributed transactions require some external transaction manager, who will be able to control all sources. Such managers are available in all J2EE servers, and we will use Jboss 5. You can read more info about protocols in wikipedia.

 
Spring transactions

Spring supports two strategies: program transaction demarcation and declarative one. If we use program way we have manually code our java classes to start transaction, commit it or rollback.

Other one way is to declare which operations we want to join in single transaction. We do this by configuring spring context in separate xml file and our business code is fully separated form transaction management. This will improve code design and component reuse.

Our environment.

First we have to create datasources. We use mysql 5 server to create two databases Alfa and Beta. Alfa has two tables: alfa1 and alfa2; each table has two fields id and name. Also we will make name column unique for alfa1 table.

Database Beta has single table: beta1 with the same columns and unique index.

Note: all mysql tables must use InnoDB engine to support transaction.

See samples for sql and instructions, how apply it to your mysql instance.

Use case

Our scenario is to have two operations, each will insert data to separate table. Table alfa2 has no indexes and we can insert any number of duplicates. Indexes on alfa1 and beta1 will refuse this possibility, so these tables must contain only unique items.

Each transaction has two steps: first insert data to alfa2 table and secondly, insert data to alfa1 (or beta1 in case of distributed process). We insert the same data twice. On first run data will be saved to both tables, and second run will break because of unique index. In case of fail, transaction is rolled back, in both tables.

Local transaction example
Local transaction is a process against single datasource, for example Alfa database. We put datasource declaration to spring context. This example has no specific features, it uses standard jdbc mysql driver and standard code design with data access object and service layer. Each database table has corresponding beans, with hibernate based mappings.
Data access object has two methods, for inserting beans to each table (alfa1 alfa2). Service layer uses dao to make actual inserts. Remember, we can insert any number of beans to alfa2, and only one item to alfa1.

Let's start with program transaction demarcation.
Create spring context (programContext.xml) and declare all beans: connection to database, hibernate configuration, dao and service. All beans use dependency injection to couple all objects.
The most important bean for us is transaction manager. We declare standard one, and inject it to our service. Take a look to Service class for transaction manager use example. It is the only one "trick".
Next step is to make some class to start our flow, it initializes spring context and calls service layer. Run against clean database must insert both beans and you should see a row in each table. If you start it again it must return exception, as index onalfa1 table will refuse insertion. Note, alfa2 also has no new record, as it was rolled back.

Now let's try to make the same job, but use declarative transaction management. Advantages for declarative management is week coupling to framework code. In previous example we use explicit transaction management, and have to write entire code manually. Also we have to use transaction manager in unit tests. We can eliminate this job, by asking spring to manage transactions.
We have to create new service layer object, without transaction manager.
Now redeclare all our staff in new spring context (declarativeContext.xml). Data sources, hibernate settings, transaction manager and beans are similar to previous example. The only one difference is in Service declaration, now it does not use transaction manager directly.
But we still want to use transaction, and have to tell framework about our rules. In general we have two things to declare: which methods to put in transaction, and what transaction manager to use.
Rule for method invocations is: execution(* service.DeclarativeTx.*(..)) - it tells framework to detect all methods from our service layer. Next setting says to use transaction manager for these methods.
Now you should clear tables again to drop rows from previous example. And run this example twice. Again, first run finishes with success, while next one throws exception(and bothdao operation are rolled back).

Distributed transaction example
Local transactions are enough for most applications with single database connection. Sometimes, application uses more then one datasource: two databases or jms plus database; and it would be great to share single transaction against all sources. Here we work with distributed transaction management.
In general, distributed transactions are similar to local ones, but it uses other API - JTA, to allow multiple datasources to synchronize. Also we need some system to manage this synchronization: distributed transaction manager. There are a lot of managers available, open and close sourced, all of them require some configuration to work standalone. But we choose more optimal way...
Most J2EE servers include transaction managers. And we will use Jboss 5.
Our goal: we build web application with single flow, this flow calls service layer to insert beans to different data sources. We deploy this application tojboss 5.
Jboss will be responsible for data sources and transaction management. And our application accesses these data sources via jndi.
Note: you have to deploy both datasources to jboss. They must use XA drivers, as usual ones does not support distributed processes (read about two-phase commit in wiki). Example for jboss configs is in zip; you may want to change access data.

Let's start with application: create dynamic web application in eclipse and add required jars (you may copy them from example).
Code is similar to local transaction example: beans are mapped to tables via hibernate, data access layer is responsible for bean manipulations and service layer calls dao. Of course you have to create daos for each data source.
We will use declarative transaction management, so we can skip any thoughts about transactions for now.
Now we have to create web flow, using spring. We create controller and view - check spring web application development documentation for details.
Controller makes simple job - calls our service. As usual, first call finishes ok, and second one rises exception, and both datasources roll changes back.
Take a look at spring context, changes to local transaction details are:
- two datasources mapped via jndi
- hibernate configurations to both data sources
- JtaTransactionManager
- configuration for web flow

The most important thing is JtaTransactionManager declaration. This class knows JNDI paths to managers in all main JEE servers, including jboss. So it maps to jboss manager automatically. In stand alone application you have to configure manager manually.

How to run distributed example

Create both databases. Download and unpack jboss5. Copy datasources configs from example archive and tune them with your access data.
Deploying datasources is simple, just copy them to jboss/server/default/deploy.
Pack you application as xa.wat and copy WAR file to the same location (deploy).
Start jboss by starting jboss/bin/run.bat (or run.sh)and, if everything is ok, you may open page:
http://localhost:8080/xa/testxa.do
As usual, first run works fine, and second one returns exception.

Andrew Romanenco
andrew@romanenco.com
www.romanenco.com/springtx
You can download sample from sf.net: download samples

download text in PDF




         romanenco.com 2008