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:
datasourceneeded to connect via JDBC.
jtaUserTransactionobjects have been added to prepare for Spring's configuration for JTA transactions.
springTransactionManagerobject has been added to tell Spring it needs to use JTA.
BankDAOhas been renamed to
bankTarget(for the reason explained below).
bankobject has been added to wrap transactions around all of the methods of the
bankTarget. We configured the
bankobject to use the
springTransactionManager, meaning that all transactions will be JTA transactions. The transaction setting is
PROPAGATION_REQUIREDfor each method, and rollback is forced on any
Of all of these objects, you can easily copy and paste
springTransactionManager to other projects. The only application-specific objects are the
bankTarget, and 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 our
- When a method of the bank is called, this call goes via the proxy.
- The proxy uses the
springTransactionManagerto create a new transaction.
springTransactionManagerwas configured to use JTA, so it delegates to the JTA.
- The call is now forwarded to the actual
datasourceit received from Spring.
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 be
- 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