Chapter 35. Transactions QuickStart

35.1. Introduction

The Transaction Quickstart demonstrates Spring's transaction management features. The database schema are two simple tables, credit and debit, which contain an Identifier and an Amount. The quick start shows the use of declarative transactions using attributes and also the ability to change the transaction manager (local or distributed) via changes to only the configuration files - no code changes are required.

35.2. Application Overview

The design of the application is very simple and consists of two logical layers, a business service layer in the namespace Spring.TxQuickStart.Services and a DAO layer in the namespace Spring.TxQuickStart.Dao. As this is just a toy example the business service layer does nothing more than call two DAO objects. The business service is to transfer money in a bank account and is blatantly taken from the book Pro ADO.NET by Sahil Malik. The transfer service is defined by the interface IAccountManager with the implementation AccountManager located in the namespace Spring.TxQuickStart.Services. The money is 'contained' in a credit table and a debit table in the database. The SQL Server schema for the tables is located in the file CreditsDebitsSchema.sql. Transferring the money requires an ACID operation on these two tables. The credit operation is defined via a IAccountCreditDao interface and the debit operation via an IAccountDebitDao interface. Implementations based on AdoTemplate are in the namespace Spring.TxQuickStart.Dao.Ado. Note that Spring's transaction management framework allows the mixing of data access technologies within the same transaction, i.e. ORM and ADO.NET. A demonstration of this features will be added to this quick start in a future release.

35.2.1. Interfaces

The Manager and DAO interfaces are shown below

    public interface IAccountManager
    {
        void DoTransfer(float creditAmount, float debitAmount);
    }


    public interface IAccountCreditDao
    {
        void CreateCredit(float creditAmount);
    }

    public interface IAccountDebitDao
    {
        void DebitAccount(float debitAmount);
    }

35.3. Implementation

The implementation of the Account Credit DAO is shown below

    public class AccountCreditDao : AdoDaoSupport, IAccountCreditDao
    {
        public void CreateCredit(float creditAmount)
        {
            AdoTemplate.ExecuteNonQuery(CommandType.Text,
                    String.Format("insert into Credits (CreditAmount) VALUES ({0})",
                    creditAmount));
        }
    }

and for the Debit DAO

    public class AccountDebitDao : AdoDaoSupport, IAccountDebitDao
    {
        public void DebitAccount(float debitAmount)
        {
            AdoTemplate.ExecuteNonQuery(CommandType.Text,
                                        String.Format("insert into dbo.Debits (DebitAmount) VALUES ({0})",
                                                      debitAmount));
        }
    }

Both of these DAO implementations inherit from Spring's AdoDaoSupport class that provides convenient access to an AdoTemplate for performing data access operations. With no other properties that can be configured in these implementations, the only configuration required is setting of AdoDaoSupport's DbProvider property representing the connection to the database.

The implementation of the service layer class, IAccountManager, is show below with extraneous declarations of properties for the DAO objects and boolean 'throwException' property.

    public class AccountManager : IAccountManager
    {

        // fields and properties for DAO objects and boolean throwException not shown


        [Transaction]
        public void DoTransfer(float creditAmount, float debitAmount)
        {
            creditDao.CreateCredit(creditAmount);

            if (ThrowException)
            {
                throw new ArithmeticException("Couldn't do the math....");
            }
            
            debitDao.DebitAccount(debitAmount);
        }

    }

The throw exception property, if true, will throw the shown exception and is used to demonstrate rollback behavior. Note the Transaction annotation above the method. This will be read by Spring and used to create a transactional proxy to AccountManager in order to perform declarative transaction management.

The driver for the program is an NUnit test. It is convenient to download a VS.NET add-in, such as TestDriven.NET or Resharper to be able to run the unit test from within VS.NET. The code for the unit test driver is shown below

    [TestFixture]
    public class AccountManagerTests 
    {
        private IApplicationContext ctx;

        [SetUp]
        public void SetUp()
        {
            // Configure Spring programmatically
            NamespaceParserRegistry.RegisterParser(typeof(DatabaseNamespaceParser));
            NamespaceParserRegistry.RegisterParser(typeof(TxNamespaceParser));
            NamespaceParserRegistry.RegisterParser(typeof(AopNamespaceParser));
            string ctxName = "DTCAppContext.xml"; // for .NET 2.0
            //string ctxName = "DTC1.1AppContext.xml"; // for .NET 1.1
            ctx = new XmlApplicationContext(
                "assembly://Spring.TxQuickStart.Tests/Spring.TxQuickStart/" + ctxName);            
        }

        [Test]
        public void DeclarativeWithAttributes()
        {
            IAccountManager mgr = ctx["accountManager"] as IAccountManager;
            mgr.DoTransfer(217, 217);
        }

    }

The essential element is to create an instance of Spring's application context where the relevant layers of the application are 'wired' together. The top level object for the purposes of this quick start is the account manager, which is retrieved from Spring's application context and the method DoTransfer is executed.

Note, future releases will instead have as the driver program a console application.

35.4. Configuration

The configuration for application is shown below

<objects xmlns='http://www.springframework.net'
         xmlns:db="http://www.springframework.net/database"
         xmlns:tx="http://www.springframework.net/tx"
         xmlns:aop="http://www.springframework.net/aop">

  <!-- Database Providers -->

  <db:provider id="DebitDbProvider"
                 provider="System.Data.SqlClient"
                 connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Debits;User ID=springqa; Password=springqa"/>

  <db:provider id="CreditDbProvider"
                 provider="System.Data.SqlClient"
                 connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=Credits;User ID=springqa; Password=springqa"/>


  <!-- Transaction Manager if using two databases, one containing the credit table and the other a debit table -->

  <object id="transactionManager"
          type="Spring.Data.Core.TxScopeTransactionManager, Spring.Data">
  </object>


  <!-- DAO Layer  -->

  <object id="accountCreditDao" type="Spring.TxQuickStart.Dao.Ado.AccountCreditDao, Spring.TxQuickStart">
    <property name="DbProvider" ref="CreditDbProvider"/>
  </object>

  <object id="accountDebitDao" type="Spring.TxQuickStart.Dao.Ado.AccountDebitDao, Spring.TxQuickStart">
    <property name="DbProvider" ref="DebitDbProvider"/>
  </object>


  <!-- The service layer that performs multiple data access operations -->
  <object id="accountManager"
          type="Spring.TxQuickStart.Services.AccountManager, Spring.TxQuickStart">
    <property name="AccountCreditDao" ref="accountCreditDao"/>
    <property name="AccountDebitDao" ref="accountDebitDao"/>
    <property name="ThrowException" value="false"/>
  </object>

  <!-- Enable declarative transaction management -->

  <tx:attribute-driven/>

</objects>

Moving from top to bottom in th configuration file, the database type and connection parameters are first specified for the two databases. The type of transaction manager is then selected, in this case we are showing the use of TxScopeTransactionManager that uses .NET 2.0 System.Transactions as the implementation, allowing for distributed transactions between the two different databases listed. The DAO layer is then configured with each DAO object referring to its appropriate database. The service layer then ties together the two DAO objects and configures the AccountManager not to throw exceptions. Lastly, declarative transaction management through the use of attributes is enable. In a larger application the different layers would typically be broken up into individual configuration files and imported into the main configuration file. This allows your configuration to mirror your architecture.

Running the tests will result in 217 being entered into the Credits and Debits table of each database. You should fire up SQL Server Management Studio or equivalent to verify this. It is also interesting to view the distributed transaction monitor that is part of the Component Services GUI. If you change the property ThrowException to true, and re-run the test, then you will not have the value 217 entered in either table.

If we need to switch from distributed transactions to local transactions with a single database, then all that needs to change is the configuration of the database providers as well as selecting a different transaction manager. The application code does not need to change. The configuration to use a single database is listed below

  <db:provider id="DebitDbProvider"
                 provider="System.Data.SqlClient"
                 connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa"/>

  <db:provider id="CreditDbProvider"
                 provider="System.Data.SqlClient"
                 connectionString="Data Source=MARKT60\SQL2005;Initial Catalog=CreditsAndDebits;User ID=springqa; Password=springqa"/>

  <object id="transactionManager" 
          type="Spring.Data.AdoPlatformTransactionManager, Spring.Data">
    <property name="DbProvider" ref="DbProvider"/>
  </object>

Notice that the Initial Catalog value has changed.

35.4.1. Rollback Rules

Using Rollback rules you to specify which exceptions will not cause a rollback and instead only top execution flow, committing the work done up to the exception. An alternative implementation of AccountManager's DoTransfer method (included in the sample code) is shown below.

        [Transaction(NoRollbackFor = new Type[] { typeof(ApplicationException) })]
        public void DoTransfer(float creditAmount, float debitAmount)
        {
            creditDao.CreateCredit(creditAmount);
            DoThrowException();
            debitDao.DebitAccount(debitAmount);
        }

        public void DoThrowException()
        {
            throw new ApplicationException("Testing No Rollback 'Rule'");
        }

The expected behavior is that the credit table will be updated even though the exception is thrown. This is due to specifying that exceptions of the type ApplicationException should not rollback the database transaction. Running the test code below, verifies that the exception still propagates out of the method. Use SQL Server Management Studio or equivalent to verify the state of the Credit and Debit table.

        [Test]   
        [ExpectedException(typeof(ApplicationException), "Testing No Rollback 'Rule'")]
        public void DeclarativeWithAttributesNoRollbackFor()
        {
            IAccountManager mgr = ctx["accountManager"] as IAccountManager;
            mgr.DoTransfer(314, 314);
        }

35.5. Adding additional Aspects

Transactional advice is just one type of advice that can be applied to the service layer. You can also configure other pieces of advice to be executed as part of the general advice chain that is associated with the program methods that have the Transaction attribute applied. In this example we will add logging of thrown exceptions using Spring's ExceptionHandlerAdvice. No code is required to be changed in order to have this additional functionality. Instead modify the configuration to the following

  <tx:attribute-driven order="10"/>

  <object name="exceptionAdvice" type="Spring.Aspects.Exceptions.ExceptionHandlerAdvice, Spring.Aop">
    <property name="exceptionHandlers">
      <list>
        <value>on ArithmeticException log 'Logging an exception thrown from method ' + #method.Name </value>
      </list>
    </property>
  </object>

  <object id="txAttributePointcut" type="Spring.Aop.Support.AttributeMatchMethodPointcut, Spring.Aop">
    <property name="Attribute" value="Spring.Transaction.Interceptor.TransactionAttribute, Spring.Data"/>
  </object>

  <aop:config>
    <aop:advisor id="exceptionProcessAdvisor" order="1"
                 advice-ref="exceptionAdvice" 
                 pointcut-ref="txAttributePointcut"/>
  </aop:config>

The transaction aspect is now additionally configured with an order value of "10", which will place it after the execution of the exception aspect, which is configured to use an order value of 1. The behavior for logging the exception is specified by creating and configuring an instance of Spring.Aspects.Exceptions.ExceptionHandlerAdvice. The location where that behavior is applied, the pointcut, is the Transaction attribute. The aop configuration section on the bottom is what ties together the behavior and where it will take place in the program flow. Under the covers the transaction configuration, <tx:attribute-driven/> creates similar advice and pointcut definitions. Setting the ThrowException property of AccountManager to true, you will see in the log file the message

LogExceptionHandler - Logging an exception thrown from method DoTransfer