J2EE Without the Application Server
Pages: 1, 2, 3, 4, 5, 6, 7
Step 4: Adding Declarative Transaction Management
Spring allows us to add declarative transaction management to any configured Java object. Suppose we want to make sure that the bank is always called with a valid transaction context. We do this by configuring a proxy object on top of the actual object. The proxy has the same interface as the actual object, so clients can use it in exactly the same way. The proxy can be configured to wrap each BankDAO method call in a transaction. The resulting configuration file is shown below. Don't be scared by the apparent volume of XML--most of the content can be reused by copying and pasting into your own projects.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN"
"http://www.springframework.org
/dtd/spring-beans.dtd">
<beans>
<!--
Use a JTA-aware DataSource
to access the DB transactionally
-->
<bean id="datasource"
class="com.atomikos.jdbc.nonxa.NonXADataSourceBean">
<property name="user">
<value>sa</value>
</property>
<property name="url">
<value>jdbc:hsqldb:SpringNonXADB</value>
</property>
<property name="driverClassName">
<value>org.hsqldb.jdbcDriver</value>
</property>
<property name="poolSize">
<value>1</value>
</property>
<property name="connectionTimeout">
<value>60</value>
</property>
</bean>
<!--
Construct a TransactionManager,
needed to configure Spring
-->
<bean id="jtaTransactionManager"
class="com.atomikos.icatch.jta.UserTransactionManager"/>
<!--
Also configure a UserTransaction,
needed to configure Spring
-->
<bean id="jtaUserTransaction"
class="com.atomikos.icatch.jta.UserTransactionImp"/>
<!--
Configure the Spring framework to use
JTA transactions from the JTA provider
-->
<bean id="springTransactionManager"
class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<ref bean="jtaTransactionManager"/>
</property>
<property name="userTransaction">
<ref bean="jtaUserTransaction"/>
</property>
</bean>
<!-- Configure the bank to use our datasource -->
<bean id="bankTarget" class="jdbc.Bank">
<property name="dataSource">
<ref bean="datasource"/>
</property>
</bean>
<!--
Configure Spring to insert
JTA transaction logic for all methods
-->
<bean id="bank"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<property name="transactionManager">
<ref bean="springTransactionManager"/>
</property>
<property name="target">
<ref bean="bankTarget"/>
</property>
<property name="transactionAttributes">
<props>
<prop key="*">
PROPAGATION_REQUIRED, -Exception
</prop>
</props>
</property>
</bean>
</beans>
This XML file tells Spring to configure the following objects:
- The
datasourceneeded to connect via JDBC. - The
jtaTransactionManagerandjtaUserTransactionobjects have been added to prepare for Spring's configuration for JTA transactions. - The
springTransactionManagerobject has been added to tell Spring it needs to use JTA. - The
BankDAOhas been renamed tobankTarget(for the reason explained below). - The
bankobject has been added to wrap transactions around all of the methods of thebankTarget. We configured thebankobject to use thespringTransactionManager, meaning that all transactions will be JTA transactions. The transaction setting isPROPAGATION_REQUIREDfor each method, and rollback is forced on anyException.
Of all of these objects, you can easily copy and paste jtaTransactionManager, jtaUserTransaction, and springTransactionManager to other projects. The only application-specific objects are the datasource, the bankTarget, and the bank. The bank object is interesting: it is in fact a proxy to the bankTarget; it assumes the same interface. The trick is the following: when our application asks Spring to configure and return the object called bank, Spring will actually return the proxy (which looks exactly the same to our application) and this proxy will start/end transactions for us. This way, neither the application nor the Bank class itself needs to know JTA! Figure 4 illustrates what we get at this stage.

Figure 4. Architecture with declarative JTA transactions in Spring
Things now work as follows:
- The application retrieves the object named
bank. This triggers the Spring initialization process and the proxy is returned. To the application, this proxy looks and behaves like an instance of ourBankclass. - When a method of the bank is called, this call goes via the proxy.
- The proxy uses the
springTransactionManagerto create a new transaction. - The
springTransactionManagerwas configured to use JTA, so it delegates to the JTA. - The call is now forwarded to the actual
Bank, namedbankTarget. - The
bankTargetuses thedatasourceit received from Spring. - The
datasourceregisters with the transaction. - The database access happens via regular JDBC.
- Upon returning, the proxy terminates the transaction: if no exception happened in the previous sequence, then the termination instruction is
commit. Otherwise, it will berollback. - The transaction manager coordinates commit (or rollback) with the database.
How about testing at this stage? We can reuse the BankTest with its explicit transaction demarcation: because of the PROPAGATION_REQUIRED setting, the proxy will execute with the transaction context created in the BankTest.