Exploring a Transaction Management Sample

The first part of this chapter gave you a quick overview of the transaction infrastructure in Spring, but we are sure you will appreciate an example that demonstrates how to use Spring transaction support.

There are three basic ways to implement an operation that uses transactions: you can use declarative transactions, where you simply declare that a method requires a transaction, you can use source level metadata to indicate that a method requires a transaction, or you can actually write code that handles the transactions. Spring supports all three approaches; we begin with the most flexible and convenient one—declarative transaction management.

First we will show you several implementations of transaction support. Take a look at the UML diagram in Figure 12-1 to get a sense of what is in store in the Hello World application.

image from book
Figure 12-1: UML diagram for the Hello World application

AccountManager is the high-level interface we are going to use in our Main class. abstractAccountManager contains code that performs the operations of AccountManager's methods, but in protected methods, the implementation is prefixed with do. AbstractAccountManager has accountDao and historyDao fields and public setters for these DAO interfaces; it also implements InitializingBean.afterPropertiesSet() to check that the DAOs are not null. This method also calls initManager(), which is empty in AbstractAccountManager, but subclasses can override it to perform additional initialization.

As you can guess from the names of the implementation classes, DefaultAccountManager is the default implementation and it relies on declarative transaction specifications, MetadataAccountManager is an implementation that uses source level metadata to declare the transaction requirements, ProgrammaticTemplateAccountManager demonstrates usage of TransactionTemplate, ProgrammaticManagerAccountManager shows how to use PlatformTransactionManager, and finally, JTAAccountManager demonstrates how to use JTA to manage global transactions.

Working with Common Code

Let us begin with code that is common for all the transaction management models we are going to discuss. The example is going to use the well-known concept of bank accounts. In a transaction, each operation on an account is going to write a record to an account history for auditing purposes; a transfer between two accounts is in a transaction as well.

The Account and History tables are defined in the SQL script in Listing 12-3.

Listing 12-3: Account and History SQL Script

image from book
create table Accounts (     AccountId serial not null,     AccountNumber varchar(20) not null,     SortCode varchar(10) not null,     Balance decimal(10, 2) not null,     constraint PK_AccountId primary key (AccountId) );      create unique index IX_Accounts_NumberSortCode on Accounts      using btree(AccountNumber, SortCode);      create table History (     HistoryId serial not null,     Account int not null,     Operation varchar(50) not null,     Amount decimal(10, 2) not null,     TransactionDate timestamp not null,     TargetAccount int null,     constraint PK_HistoryId primary key (HistoryId),     constraint FK_Account foreign key (Account) references Accounts(AccountId) ); create index IX_History_Account on History using btree(Account); 
image from book

The data access layer for the tables created in the script above is a simple iBATIS implementation of the AccountDao and HistoryDao interfaces. The SqlMapClientAccountDao is a standard AccountDao implementation, but HistoryDao has a bit of a twist. It is implemented in UnreliableSqlMapClientHistoryDao and its insert() method randomly fails. The HistoryDao and its implementation are shown in Listing 12-4. The reason why the insert() method throws a RuntimeException randomly is to allow us to prove that only the successful transactions are committed; the unsuccessful ones are rolled back.

Listing 12-4: HistoryDao Interface and Implementation

image from book
package com.apress.prospring.ch12.data;      // in HistoryDao.java: import java.util.List;      import com.apress.prospring.ch12.domain.History;      public interface HistoryDao {     public List getByAccount(int account);     public History getById(int historyId);     public void insert(History history); }      // in UnreliableSqlClientMapHistoryDao import java.util.List; import java.util.Random; import org.springframework.orm.ibatis.support.SqlMapClientDaoSupport; import com.apress.prospring.ch12.domain.History;      public class UnreliableSqlMapClientHistoryDao      extends SqlMapClientDaoSupport      implements HistoryDao {          private Random r = new Random();          public List getByAccount(int account) {         return null;     }          public History getById(int historyId) {         return null;     }          public void insert(History history) {         if (r.nextInt(100) % 3 == 0) {             throw new RuntimeException("Foobar");         }              getSqlMapClientTemplate().insert("insertHistory", history);     }      }
image from book

This HistoryDao implementation fails in about one third of all inserts. To complete the data access layer code, in Listing 12-5, we create an AccountManager interface.

Listing 12-5: AccountManager Interface

image from book
package com.apress.prospring.ch12.business;      import java.math.BigDecimal;      import com.apress.prospring.ch12.domain.Account;      public interface AccountManager {     public void insert(Account account);     public void deposit(int accountId, BigDecimal amount);     public void transfer(int sourceAccount, int targetAccount, BigDecimal amount);     public int count(); }
image from book

We use the methods of the interface to manipulate the accounts. The insert() method creates a new account, deposit() deposits a specified sum of money to the account, transfer() moves money between accounts, and finally count() returns the number of accounts in the database.

Finally, we implement AbstractAccountManager, which is going to be a convenient super- class (see Listing 12-6).

Listing 12-6: AbstractAccountManager Implementation

image from book
package com.apress.prospring.ch12.business;      import java.math.BigDecimal; import java.util.Date;      import org.springframework.beans.factory.BeanCreationException; import org.springframework.beans.factory.InitializingBean;      import com.apress.prospring.ch12.data.AccountDao; import com.apress.prospring.ch12.data.HistoryDao; import com.apress.prospring.ch12.domain.Account; import com.apress.prospring.ch12.domain.History;      public abstract class AbstractAccountManager      implements InitializingBean, AccountManager {     private AccountDao accountDao;     private HistoryDao historyDao;          protected void doInsert(Account account) {         getAccountDao().insert(account);         History history = new History();         history.setAccount(account.getAccountId());         history.setAmount(account.getBalance());         history.setOperation("Initial deposit");         history.setTargetAccount(null);         history.setTransactionDate(new Date());         getHistoryDao().insert(history);     }          protected void doDeposit(int accountId, BigDecimal amount) {         History history = new History();         history.setAccount(accountId);         history.setAmount(amount);         history.setOperation("Deposit");         history.setTargetAccount(null);         history.setTransactionDate(new Date());              getAccountDao().updateBalance(accountId, amount);         getHistoryDao().insert(history);     }          protected void doTransfer(int sourceAccount,          int targetAccount, BigDecimal amount) {         Account source = getAccountDao().getById(sourceAccount);         Account target = getAccountDao().getById(targetAccount);              if (source.getBalance().compareTo(amount) > 0) {             // transfer allowed             getAccountDao().updateBalance(sourceAccount, amount.negate());             getAccountDao().updateBalance(targetAccount, amount);                  History history = new History();             history.setAccount(sourceAccount);             history.setAmount(amount);             history.setOperation("Paid out");             history.setTargetAccount(target);             history.setTransactionDate(new Date());             getHistoryDao().insert(history);                  history = new History();             history.setAccount(targetAccount);             history.setAmount(amount);             history.setOperation("Paid in");             history.setTargetAccount(source);             history.setTransactionDate(new Date());             getHistoryDao().insert(history);         } else {             throw new RuntimeException("Not enough money");         }     }          protected int doCount() {         return getAccountDao().getCount();     }                   public final void afterPropertiesSet() throws Exception {         if (accountDao == null) throw new              BeanCreationException("Must set accountDao");         if (historyDao == null) throw new              BeanCreationException("Must set historyDao");         initManager();     }          protected void initManager() {              }          protected AccountDao getAccountDao() {         return accountDao;     }          public void setAccountDao(AccountDao accountDao) {         this.accountDao = accountDao;     }          protected HistoryDao getHistoryDao() {         return historyDao;     }          public void setHistoryDao(HistoryDao historyDao) {         this.historyDao = historyDao;     } } 
image from book

As you can see, the AbstractAccountManager is totally oblivious to transactions; the code we wrote simply assumes that everything succeeds and does not deal with any failures. AbstractAccountManager provides implementation for methods of the AccountManager inter- face, with a do prefix to allow the implementation classes to simply call the do methods. It also implements the afterPropertiesSet() method of InitializingBean as final to prevent the subclasses from overriding it. However, some subclasses may need to perform additional initialization, therefore AbstractAccountManager contains the initManager() method that is called in afterPropertiesSet() and can be overridden in subclasses.

With this infrastructure in place, we can start looking at concrete ways to implement transaction management in Spring.

Supporting Declarative Transactions

In this context, declarative means that we tell Spring that a method on a bean has transactional properties and Spring makes sure that the appropriate transaction exists when the method is called. This approach heavily relies on AOP to intercept method calls. For a more detailed description of AOP, refer to Chapter 6.

The major advantage of declarative transaction definition is that we do not have to modify the code we have. When implementing the method, we do not add any transaction management at all. Further, it allows us to specify groups of methods that require the same transaction settings. It is not difficult to imagine that we can decide that all methods of a bean require a serializable isolation level, but further down the line, we realize that we can lower the isolation level to READ_COMMITTED. If the configuration file states that, "all methods of the bean require transactions with a serializable isolation level," we can change just one line to downgrade the isolation level.

Another advantage is that the proxy Spring is going to create to process the calls to the target bean allows us to specify additional interceptors. Even though it is not used in this example, we show you how to use this in the SpringBlog sample application.

Let us begin by implementing DefaultAccountManager (see Listing 12-7); remember that all we need to do is call AbstractAccountManager's do*() methods to perform all the work.

Listing 12-7: DefaultAccountManager Implementation

image from book
package com.apress.prospring.ch12.business;      import java.math.BigDecimal; import java.util.Date;      import com.apress.prospring.ch12.data.AccountDao; import com.apress.prospring.ch12.data.HistoryDao; import com.apress.prospring.ch12.domain.Account; import com.apress.prospring.ch12.domain.History;      public class DefaultAccountManager extends AbstractAccountManager {          public void insert(Account account) {         doInsert(account);     }          public void deposit(int accountId, BigDecimal amount) {         doDeposit(accountId, amount);     }          public void transfer(int sourceAccount, int targetAccount, BigDecimal amount) {         doTransfer(sourceAccount, targetAccount, amount);     }      }
image from book

Looking back at the AbstractAccountManager shown in Listing 12-6, you can see that the doInsert(), doDeposit(), and doTransfer() methods require a transaction. This means that you must execute these in an existing transaction or, if no transaction exists, you must start a new one in which to execute them. Because our example bank is very generous, it allows the customers to withdraw (deposit negative amounts) from the account as much as they want, and because creating a new account does not update any data that may be referenced by other transactions, an ISOLATION_READ_COMMITTED isolation level is sufficient. However, the transfer() method inserts new data and updates existing data. It is vital to ensure that no other transactions can select the newly inserted or updated data, thus you must use the highest isolation level, ISOLATION_SERIALIZABLE.

Now that we have the code for the application ready, we need to wire the application up in Spring context file (see Listing 12-8).

Listing 12-8: applicationContext.xml for the Sample Application

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>          <!-- Data source bean -->     <bean            destroy-method="close">         <property name="driverClassName">             <value>org.postgresql.Driver</value></property>         <property name="url">             <value>jdbc:postgresql://localhost/prospring</value></property>         <property name="username"><value>janm</value></property>         <property name="password"><value>****</value></property>     </bean>           <bean bold">transactionManager"          bold">org.springframework.jdbc.datasource.DataSourceTransactionManager">         <property name="dataSource"><ref local="dataSource"/></property>     </bean>          <bean           >         <property name="configLocation"><value>sqlMapConfig.xml</value></property>     </bean>          <bean           >         <property name="dataSource"><ref local="dataSource"/></property>         <property name="sqlMapClient"><ref local="sqlMapClient"/></property>     </bean>          <bean           >         <property name="dataSource"><ref local="dataSource"/></property>         <property name="sqlMapClient"><ref local="sqlMapClient"/></property>      </bean>           <bean bold">accountManagerTarget"          >         <property name="accountDao"><ref local="accountDao"/></property>         <property name="historyDao"><ref local="historyDao"/></property>     </bean>          <bean           bold">org.springframework.transaction.interceptor ¿                    TransactionProxyFactoryBean">         <property name="transactionManager">             <ref bean="transactionManager"/></property>         <property name="target"><ref local="accountManagerTarget"/></property>         <property name="transactionAttributes">             <props>                 <prop key="insert*">                     PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>                 <prop key="transfer*">                     PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE</prop>                 <prop key="deposit*">                     PROPAGATION_REQUIRED, ISOLATION_READ_COMMITTED</prop>             </props>         </property>     </bean> </beans>
image from book

There are a lot of new things in this context file. Let's take a look at the most important bits in more detail.

First of all, we have defined a transactionManager bean, which is implemented in DataSourceTransactionManager. This transaction manager needs to access the dataSource bean to control the JDBC transactions. This is still a fairly standard Spring configuration; the most important and interesting part is that our accountManager bean is actually a proxy to accountManagerTarget. The proxy takes care of the transaction management using the transactionManager bean. When a call to a method defined in transactionAttributes is made on a proxy, the proxy uses the transactionManager to set up the transaction (if needed) and calls the appropriate method on the target. If the target method throws an exception, the proxy instructs the transaction manager to roll back the active transaction. If the target method succeeds, the transaction is committed.

Note 

You can specify exceptions that are allowed to be thrown from the target method and you can define what the proxy is going to do when it catches the exception. By default, the transaction manager rolls back the transaction if a RuntimeException is thrown and commits the transaction if a checked exception is thrown. You can fine tune this behavior in the manager bean declaration: If you prefix the exception name with a minus (–), the proxy rolls back the transaction; if you prefix the exception name with a plus (+), the proxy commits the transaction. You need to be very careful of the consequences when you specify exceptions that trigger a commit and be absolutely sure you mention this in the documentation!

If we take a more detailed look at the transactionAttributes property, we can see that it represents a Properties instance, where key is the method name and the value is made up of the transaction propagation, the isolation level, and a list of exceptions. The names are self- explanatory, as you can see here:

<prop key="transfer*">PROPAGATION_REQUIRED, ISOLATION_SERIALIZABLE</prop>

This code defines that transfer method. Any parameters are included in a transaction if it exists, or a new transaction is started before its execution and the transaction isolation level is set or upgraded to ISOLATION_SERIALIZABLE.

The Main class of the sample application uses the accountManager bean to perform 100 account inserts. Because the historyDao is intentionally unreliable, we should get about 66 successful inserts and 34 failures.

Listing 12-9: Main Class

image from book
package com.apress.prospring.ch12;      import java.math.BigDecimal; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.apress.prospring.ch12.business.AccountManager; import com.apress.prospring.ch12.domain.Account;      public class Main {          private ApplicationContext context;          private void run() {         System.out.println("Initializing application");         context = new ClassPathXmlApplicationContext(new String[] {              "applicationContext.xml", "applicationContext-local.xml" });         AccountManager manager = (AccountManager)context.getBean(             "accountManager");                  int count = manager.count();         int failures = 0;         int attempts = 100;                  for (int i = 0; i < attempts; i++) {             Account a = new Account();             a.setBalance(new BigDecimal(10));             a.setNumber("123 " + i);             a.setSortCode("xxx " + i);             try {                 manager.insert(a);             } catch (RuntimeException ex) {                 System.out.println("Failed to insert account " + ex.getMessage());                 failures++;             }         }                  System.out.println("Attempts  : " + attempts);         System.out.println("Failures  : " + failures);         System.out.println("Prev count: " + count);         System.out.println("New count : " + manager.count());                  System.out.println("Done");     }          public static void main(String[] args) {         new Main().run();     }      }
image from book

Running this application produces the following output:

... DefaultListableBeanFactory:182 - Invoking BeanPostProcessors after ¿ initialization of bean 'accountManager' DefaultListableBeanFactory:538 - Calling code asked for FactoryBean ¿ instance for name 'accountManager' DefaultListableBeanFactory:157 - Returning cached instance of singleton ¿ bean 'accountManager' DefaultListableBeanFactory:522 - Bean with name 'accountManager' is a factory bean … TransactionInterceptor:196 - Getting transaction for method 'insert' in ¿ class [com.apress.prospring.ch12.business.AccountManager] DataSourceTransactionObject:60 - JDBC 3.0 Savepoint class is available DataSourceTransactionManager:195 - Using transaction object ¿ [org.springframework.jdbc.datasource.DataSourceTransactionObject@1d381d2] DataSourceTransactionManager:267 - Creating new transaction DataSourceTransactionManager:153 - Opening new connection for JDBC transaction DataSourceUtils:207 - Changing isolation level of JDBC connection ¿ [org.apache.commons.dbcp.PoolableConnection@afa68a] to 2 DataSourceTransactionManager:170 - Switching JDBC connection ¿ [org.apache.commons.dbcp.PoolableConnection@afa68a] to manual commit TransactionSynchronizationManager:142 - Bound value ¿ [org.springframework.jdbc.datasource.ConnectionHolder@1dec1dd] for key ¿ [org.apache.commons.dbcp.BasicDataSource@1ee2c2c] to thread [main] TransactionSynchronizationManager:194 - Initializing transaction synchronization TransactionSynchronizationManager:117 - Retrieved value ¿ [org.springframework.jdbc.datasource.ConnectionHolder@1dec1dd] for key ¿ [org.apache.commons.dbcp.BasicDataSource@1ee2c2c] bound to thread [main] Connection:24 - {conn-100003} Connection PreparedStatement:30 - {pstm-100004} PreparedStatement:¿     select nextval('Accounts_AccountId_Seq') PreparedStatement:31 - {pstm-100004} Parameters: [] PreparedStatement:32 - {pstm-100004} Types: [] ResultSet:25 - {rset-100005} ResultSet ResultSet:45 - {rset-100005} Header: [nextval] ResultSet:49 - {rset-100005} Result: [6] PreparedStatement:30 - {pstm-100006} PreparedStatement:¿     insert into Accounts (AccountId, Number, SortCode, Balance)¿     values (?, ?, ?, ?) PreparedStatement:31 - {pstm-100006} Parameters: [6, 123 0, xxx 0, 10] PreparedStatement:32 - {pstm-100006} Types: [java.lang.Integer, ¿ java.lang.String, java.lang.String, java.math.BigDecimal] TransactionSynchronizationManager:117 - Retrieved value ¿ [org.springframework.jdbc.datasource.ConnectionHolder@1dec1dd] for key ¿ [org.apache.commons.dbcp.BasicDataSource@1ee2c2c] ¿ bound to thread [main] Connection:24 - {conn-100007} Connection PreparedStatement:30 - {pstm-100008} PreparedStatement:¿     select nextval('History_HistoryId_Seq') PreparedStatement:31 - {pstm-100008} Parameters: [] PreparedStatement:32 - {pstm-100008} Types: [] ResultSet:25 - {rset-100009} ResultSet ResultSet:45 - {rset-100009} Header: [nextval] ResultSet:49 - {rset-100009} Result: [7] PreparedStatement:30 - {pstm-100010} PreparedStatement:¿     insert into History (HistoryId, Account, Operation, Amount, ¿     TransactionDate, TargetAccount) ¿     values (?, ?, ?, ?, ?, ?)¿ PreparedStatement:31 - {pstm-100010} Parameters: ¿ [7, 6, Initial deposit, 10, 2004-09-16 12:38:12.533, null] PreparedStatement:32 - {pstm-100010} Types: ¿ [java.lang.Integer, java.lang.Integer, java.lang.String, ¿ java.math.BigDecimal, java.sql.Timestamp, null] TransactionInterceptor:239 - Invoking commit for transaction on ¿ method 'insert' in class ¿ [com.apress.prospring.ch12.business.AccountManager] DataSourceTransactionManager:495 - Triggering beforeCommit synchronization DataSourceTransactionManager:510 - Triggering beforeCompletion synchronization DataSourceTransactionManager:372 - Initiating transaction commit DataSourceTransactionManager:202 - Committing JDBC transaction on ¿ connection [org.apache.commons.dbcp.PoolableConnection@afa68a] DataSourceTransactionManager:540 - Triggering afterCompletion synchronization TransactionSynchronizationManager:234 - Clearing transaction synchronization TransactionSynchronizationManager:165 - Removed value  ... Connection:24 - {conn-100671} Connection PreparedStatement:30 - {pstm-100672} PreparedStatement:¿     select count(*) from Accounts PreparedStatement:31 - {pstm-100672} Parameters: [] PreparedStatement:32 - {pstm-100672} Types: [] ResultSet:25 - {rset-100673} ResultSet ResultSet:45 - {rset-100673} Header: [count] ResultSet:49 - {rset-100673} Result: [67] ...      Attempts  : 100 Failures  : 33 Prev count: 0 New count : 67 Done

As you can see from the output, when a method in Main asks for an instance of accountManager, Spring creates the proxy to the accountManagerTarget. When a method is then called on the accountManager proxy, Spring intercepts the method call and decides whether the method needs to be enlisted in a transaction. The insert() method required a transaction, and the proxy creates and manages the transaction; the count() method does not need a transaction, and the proxy simply delegates the call to the accountManagerTarget method without any transaction processing. Finally, the results are consistent with our expectations, the (intentionally) unreliable implementation of HistoryDao failed in a third of all calls to its insert() method, resulting in 67 commits and 33 rollbacks.

Supporting Source Level Metadata Transactions

Source level metadata is a special type of declarative transaction support. Rather than defining the transaction requirements in the Spring context file, you define the transaction requirements directly in the source code. By default, Spring includes support for Jakarta Commons Attributes, more specifically, Commons Attributes 2.1. These attributes look very much like Javadoc comments, as you can see in Listing 12-10.

Listing 12-10: Commons Attributes Declaration

image from book
/**  * Tranditional Javadoc comments  * @author janm  * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()  */ public class Foo {      /**      * Javadoc comments      * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()      */     public void bar() {     } }
image from book

Listing 12-10 shows two attributes: a class-wide attribute and a method attribute. Commons Attributes are not part of standard Java 1.4 SDK, therefore the source code that uses attributes must be compiled. The most convenient way to do this is to create an Ant taskdef. You must add the Commons Attributes compiler and the API, the XDoclet, and the commons-collection JAR files to $ANT_HOME/lib; you must also add a taskdef element to the Ant build.xml file.

Listing 12-11: Build.xml taskdef for Commons Attributes Compiler

image from book
    <taskdef resource="org/apache/commons/attributes/anttasks.properties"/>
image from book

The next step is to create a target that is going to process the source files, compile the attributes, and place the generated files to the dir.build.commons-attributes directory.

Listing 12-12: compileAttributes Ant Target

image from book
   <target name="compile-attributes" >        <attribute-compiler destdir="${dir.build.commons-attributes}">            <fileset dir="${dir.main.java.src}" includes="**/*.java"/>        </attribute-compiler>    </target> 
image from book

The final change to the build.xml script (see Listing 12-13) is to modify the compile target to depend on compile-attributes and to compile the generated metadata sources.

Listing 12-13: Modified Compile Target

image from book
   <target name="compile" depends="init, compile-attributes">       <javac destdir="${dir.main.build}" debug="on"          debuglevel="lines,vars,source">          <classpath ref/>             <src path="${dir.build.commons-attributes}"/>             <src path="${dir.main.java.src}"/>       </javac>     </target> 
image from book

Now that we have the Ant build file ready, we can proceed to implement the MetadataAccountManager as a subclass of AbstractAccountManager (see Listing 12-14). This class is going to look very much like DefaultAccountManager, the only difference is the use of Commons Attributes.

Listing 12-14: MetadataAccountManager Implementation

image from book
package com.apress.prospring.ch12.business;      import java.math.BigDecimal;      import com.apress.prospring.ch12.domain.Account;      /**  * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()  */ public class MetadataAccountManager extends AbstractAccountManager {         public void insert(Account account) {       doInsert(account);    }         public void deposit(int accountId, BigDecimal amount) {       doDeposit(accountId, amount);    }         public void transfer(int sourceAccount, int targetAccount, BigDecimal amount) {       doTransfer(sourceAccount, targetAccount, amount);    }         public int count() {       return doCount();    }      }
image from book

Next, we take the simplistic approach and mark the entire class with DefaultTransactionAttribute. However, something is still missing. How can Spring know how to use the attributes? After all, the attributes are not compiled in the MetadataAccountManager.class file. We need to configure Spring AOP autoproxy support. In Listing 12-15, we show you just the basic configuration; we discuss AOP autoproxies in more detail in

Listing 12-15: AOP Autoproxies Definition in the Context File

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>          <bean bold">autoproxy"          symbol">¿             DefaultAdvisorAutoProxyCreator"/>     <bean bold">transactionAttributeSource"          symbol">¿             AttributesTransactionAttributeSource"         autowire="constructor"/>     <bean bold">transactionInterceptor"                   autowire="byType"/>     <bean bold">transactionAdvisor"         symbol">¿             TransactionAttributeSourceAdvisor"         autowire="constructor" />     <bean bold">attributes"         />          <!-- other beans as usual -->          <bean bold">accountManager"          bold">com.apress.prospring.ch12.business.MetadataAccountManager">         <property name="accountDao"><ref local="accountDao"/></property>         <property name="historyDao"><ref local="historyDao"/></property>     </bean>      </beans>
image from book

The most important new beans from Listing 12-14 are autoproxy and transactionAdvisor. When the application requests an instance of the accountManager bean, Spring automatically creates a proxy to the original bean.

Let's take a closer look at the configuration Spring needs to perform to successfully enable transaction management using source level metadata. The attribute's bean configures support for a generic attributes framework. Spring comes with only Commons Attributes support, but you can easily implement the org.springframework.metadata.Attributes interface to add support for your favorite attributes framework. The transactionAdvisor bean provides advice to the AOP autoproxy to check whether a method call needs to be intercepted by the transactionInterceptor bean. The transactionInterceptor bean is a method call interceptor that checks whether a method or class is marked with a transactional attribute and if so, enlists it in a transaction.

The approach we have implemented in MetadataAccountManager is very simple, but not quite efficient. We have marked the entire class with DefaultTransactionAttribute, which means that all its methods are enlisted in a transaction. Usually there are several methods that do not require transactions; in our example, it is the count() method that does not need to be enlisted in a transaction. To make the example a bit more efficient, in Listing 12-16, we remove the DefaultTransactionAttribute from the class definition and move it to the appropriate methods.

Listing 12-16: MetadataAccountManager Implementation

image from book
package com.apress.prospring.ch12.business;      import java.math.BigDecimal;      import com.apress.prospring.ch12.domain.Account;      /**  * @@org.springframework.transaction.interceptor.DefaultTransactionAttribute()  */ public class MetadataAccountManager extends AbstractAccountManager {          /**      * @@org.springframework.transaction.interceptor.¿         DefaultTransactionAttribute()      */    public void insert(Account account) {       doInsert(account);    }          /**      * @@org.springframework.transaction.interceptor.¿         DefaultTransactionAttribute()      */    public void deposit(int accountId, BigDecimal amount) {       doDeposit(accountId, amount);    }          /**      * @@org.springframework.transaction.interceptor.¿         DefaultTransactionAttribute()      */    public void transfer(int sourceAccount, int targetAccount, BigDecimal amount) {       doTransfer(sourceAccount, targetAccount, amount);    }         public int count() {       return doCount();    }      } 
image from book

We have now marked only the insert(), deposit(), and transfer() methods to make sure a transaction and the application correctly wrap calls to these methods in the AOP proxy.

From a programming and configuration point of view, using source level metadata is a very nice way to add transactional support. You have to perform very little configuration in the context files, and the transactional attributes are closely bound with the source code. On the downside, changing the transactional behavior requires you to recompile, retest, and redeploy the application. This gives a declarative transaction in the context files implementation a major advantage over source level metadata implementation.

Using Programmatic Transactions

The last way to implement transactional operations is to write the transaction management code manually. In most cases, this is not an ideal solution because you have to write a fair amount of code that you could avoid writing if you use previously discussed methods. If you choose to implement transactions programmatically, you can either use TransactionTemplate or PlatformTransactionManager; however, always try to use the first option because it is much easier to use. Using PlatformTransactionManager is very much like using JTA UserTransactions— it involves complex and chatty code.

Let's begin by discussing TransactionTemplate. This class is very similar to JdbcTemplate or SqlMapClientTemplate: you call its methods and implement a callback interface where you perform the transactional work. Naturally, TransactionTemplate instances require a PlatformTransactionManager instance; you typically set this in the Spring context file.

In Listing 12-17, we create a ProgrammaticAccountManager class as a subclass of AbstractAccountManager and call its transactional methods in a callback of TransactionTemplate.

Listing 12-17: ProgrammaticAccountManager Implementation

image from book
package com.apress.prospring.ch12.business;      import java.math.BigDecimal;      import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallbackWithoutResult; import org.springframework.transaction.support.TransactionTemplate;      import com.apress.prospring.ch12.domain.Account;      public class ProgrammaticTemplateAccountManager      extends AbstractAccountManager {          private PlatformTransactionManager platformTransactionManager;     private TransactionTemplate transactionTemplate;          protected void initManager() {         super.initManager();         transactionTemplate = new TransactionTemplate(transactionManager);     }          public void insert(final Account account) {         transactionTemplate.execute(new TransactionCallbackWithoutResult() {                  protected void doInTransactionWithoutResult(TransactionStatus status) {                 doInsert(account);             }                      });     }          public void deposit(final int accountId, final BigDecimal amount) {         transactionTemplate.execute(new TransactionCallbackWithoutResult() {                  protected void doInTransactionWithoutResult(TransactionStatus status) {                 doDeposit(accountId, amount);             }                      });     }          public void transfer(final int sourceAccount, final int targetAccount,          final BigDecimal amount) {         transactionTemplate.execute(new TransactionCallbackWithoutResult() {                  protected void doInTransactionWithoutResult(TransactionStatus status) {                 doTransfer(sourceAccount, targetAccount, amount);             }                      });     }          public int count() {         return doCount();     }          public void setPlatformTransactionManager(         PlatformTransactionManager platformTransactionManager) {         this.platformTransactionManager = platformTransactionManager;     } } 
image from book

As you can see, the implementation uses TransactionTemplate's execute method and implements TransactionCallbackWithoutResult, which means that we do not have to bother with the return value and the execute method returns null.

If we need to return a value from the transaction, we implement the TransactionCallback interface and its execute() method returns an Object. As you can see in Listing 12-18, the configuration of this bean is simpler than the configuration of the declarative bean shown in Listing 12-8.

Listing 12-18: accountManager Bean Definition

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>      <!-- other beans -->     <bean bold">accountManager"          bold">¿             ProgrammaticTemplateAccountManager">         <property name="accountDao"><ref local="accountDao"/></property>         <property name="historyDao"><ref local="historyDao"/></property>         <property name="transactionManager">             <ref bean="transactionManager"/></property>     </bean> </beans>
image from book

Just to illustrate, in Listing 12-19, we show you how to implement the AccountManager using PlatformTransactionManager's methods. However, this is the clumsiest way to do things, and we cannot see a reason why you should choose to make your life so complicated.

Listing 12-19: ProgrammaticManagerAccountManager Implementation

image from book
package com.apress.prospring.ch12.business;      import java.math.BigDecimal;      import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.DefaultTransactionDefinition;      import com.apress.prospring.ch12.domain.Account;      public class ProgrammaticManagerAccountManager      extends AbstractAccountManager {          private PlatformTransactionManager transactionManager;          private TransactionDefinition getDefinition(int isolationLevel) {         DefaultTransactionDefinition def = new DefaultTransactionDefinition(             TransactionDefinition.PROPAGATION_REQUIRED);         def.setIsolationLevel(isolationLevel);                  return def;     }          public void insert(final Account account) {         TransactionStatus status = transactionManager.getTransaction(             getDefinition(TransactionDefinition.ISOLATION_READ_COMMITTED));         try {             doInsert(account);             transactionManager.commit(status);         } catch (Throwable t) {             transactionManager.rollback(status);         }          }     // other methods omitted for clarity }
image from book

In Listing 12-19, we only show the implementation of the insert() method; we hope this is scary enough to convince you not to use this in your code. First of all, there is a lot of repetitive code to write and as a result, the chances of making a coding mistake are much higher. Overall, the concept is quite simple: obtain the TransactionStatus, perform any transactional work, and if it all succeeds, commit the work; otherwise roll it back. As you can see in Listing 12-20, the bean definition remains very similar to the definition from Listing 12-18.

Listing 12-20: accountManager Bean Definition

image from book
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" ¿ "http://www.springframework.org/dtd/spring-beans.dtd">      <beans>      <!-- other beans -->     <bean bold">accountManager"          bold">¿             ProgrammaticManagerAccountManager">         <property name="accountDao"><ref local="accountDao"/></property>         <property name="historyDao"><ref local="historyDao"/></property>         <property name="transactionManager">             <ref bean="transactionManager"/></property>     </bean> </beans>
image from book

As you can see, there is far too much code to write, and you are not getting any added benefits from declarative or metadata transaction management.



Pro Spring
Pro Spring
ISBN: 1590594614
EAN: 2147483647
Year: 2006
Pages: 189

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net