Chapter 17. Transaction management

17.1. Introduction

Spring.NET provides a consistent abstraction for transaction management that provides the following benefits

  • Provides a consistent programming model across different transaction APIs such as ADO.NET, Enterprise Services, System.Transactions, and NHibernate.

  • Support for declarative transaction management with any of the above data access technologies

  • Provides a simple API for programmatic transaction management

  • Integrates with Spring's high level persistence integration APIs such as AdoTemplate.

This chapter is divided up into a number of sections, each detailing one of the value-adds or technologies of the Spring Framework's transaction support. The chapter closes with some discussion of best practices surrounding transaction management.

  • The first section, entitled Motivations describes why one would want to use the Spring Framework's transaction abstraction as opposed to using System.Transactions or a specific data access technology transaction API.

  • The second section, entitled Key Abstractions outline the core classes as well as how to configure them.

  • Th third section, entitled Declarative transaction management, covers support for declarative transaction management.

  • The fourth section, entitled Programmatic transaction management, covers support for programmatic transaction management.

17.2. Motivations

The data access technology landscape is a broad one, within the .NET BCL there are three APIs for performing transaction management, namely ADO.NET, Enterprise Services, and System.Transactions. Other data access technologies such as object relational mappers and result-set mapping libraries are also gaining in popularity and each come with their own APIs for transaction management. As such, code is often directly tied to a particular transaction API which means you must make an up-front decision which API to use in your application. Furthermore, if the need arises to change your approach, it quite often will not be a simple refactoring. Using Spring's transaction API you can keep the same API across different data access technologies. Changing the underlying transaction implementation that is used is a simple matter of configuration or a centralized programmatic change as compared to a major overhauling.

Hand in hand with the variety of options available is the establishment generally agreed upon best practices for data access. Martin Fowler's book, Patterns of Enterprise Application Architecture, is an excellent source of approaches to data access that have been successful in the real world. One approach that is quite common is to introduce a data access layer into your architecture. The data access layer is concerned not only with providing some portability between different data access technologies and databases but its scope is strictly related to data access. A simple data access layer would be not much more than data access objects (DAOs) with 'Create/Retrieve/Update/Delete' (CRUD) methods devoid of any business logic. Business logic resides in another application layer, the business service layer, in which business logic will call one or more DAOs to fulfill a higher level end-user function.

In order to perform this end-user function with all-or-nothing transactional semantics, the transaction context is controlled by the business service layer (or other 'higher' layers). In such a common scenario, an important implementation detail is how to make the DAO objects aware of the 'outer' transaction started in another layer. A simplistic implementation of a DAO would perform its own connection and transaction management, but this would not allow grouping of DAO operations with the same transaction as the DAO is doing its own transaction/resource management. As such there needs to be a means to transfer the connection/transaction pair managed in the business service layer to the DAOs. There are a variety of ways to do this, the most invasive being the explicitly pass a connection/transaction object as method arguments to your DAOs. Another way is to store the connection/transaction pair in thread local storage. In either case, if you are using ADO.NET you must invent some infrastructure code to perform this task.

But wait, doesn't Enterprise Services solve this problem - and what about the functionality in the System.Transactions namespace? The answer is yes...and no. Enterprise Services lets you use the 'raw' ADO.NET API within a transaction context such that multiple DAO operations are grouped within the same transaction. The downside to Enterprise Services is that it always uses distributed (global) transactions via the Microsoft Distributed Transaction Coordinator (MS-DTC). For most applications this is overkill just to get this functionality as global transactions are significantly less performant than local ADO.NET transactions.

There are similar issues with using the 'using TransactionScope' construct within the new System.Transactions namespace. The goal with TransactionScope is to define a, well - transaction scope - within a using statement. Plain ADO.NET code within that using block will then be a local ADO.NET based transaction if only a single transactional resource is accessed. However, the 'magic' of System.Transactions (and the database) is that local transactions will be promoted to distributed transactions when a second transaction resource is detected. The name that this goes by is Promotable Single Phase Enlistment (PSPE). However, there is a big caveat - opening up a second IDbConnection object to the same database with the same database string will trigger promotion from local to global transactions. As such, if your DAOs are performing their own connection management you will end up being bumped up to a distributed transaction. In order to avoid this situation for the common case of an application using a single database, you must pass around a connection object to your DAOs. It is also worth to note that many database providers (Oracle for sure) do not yet support PSPE and as such will always use a distributed transaction even if there is only a single database.

Last but not least is the ability to use declarative transaction management. Not many topics in database transaction-land give developers as much 'bang-for-the-buck' as declarative transactions since the noisy tedious bits of transactional API code in your application are pushed to the edges, usually in the form of class/method attributes. Only Enterprise Services offers this feature in the BCL. Spring fills the gap - it provides declarative transaction management if you are using local ADO.NET or System.Transactions (the most popular) or other data access technologies. Enterprise Services is not without it small warts as well, such as the need to separate your query/retrieve operations from your create/update/delete operations if you want to use different isolation levels since declarative transaction metadata can only be applied at the class level. Nevertheless, all in all, Enterprise Services, in particular with the new 'Services Without Components' implementation for XP SP2/Server 2003, and hosted within the same process as your application code is as good as it gets out of the .NET box. Despite these positive points, it hasn't gained a significant mindshare in the development community.

Spring's transaction support aims to relieve these 'pain-points' using the data access technologies within the BCL - and for other third party data access technologies as well. It provides declarative transaction management with a configurable means to obtain transaction option metadata - out of the box attributes and XML within Spring's IoC configuration file are supported.

Finally, Spring's transaction support lets you mix data access technologies within a single transaction - for example ADO.NET and NHibernate operations.

With this long winded touchy/feely motivational section behind us, lets move on to see the code.

17.3. Key Abstractions

The key to the Spring transaction management abstraction is the notion of a transaction strategy. A transaction strategy is defined by the Spring.Transaction.IPlatformTransactionManager interface, shown below:

public interface IPlatformTransactionManager {

  ITransactionStatus GetTransaction( ITransactionDefinition definition );

  void Commit( ITransactionStatus transactionStatus );

  void Rollback( ITransactionStatus transactionStatus );

}

This is primarily a 'SPI' (Service Provider Interface), although it can be used Programatically. Note that in keeping with the Spring Framework's philosophy, IPlatformTransactionManager is an interface, and can thus be easily mocked or stubbed as necessary. IPlatformTransactionManager implementations are defined like any other object in the IoC container. The following implementations are provided

  • AdoPlatformTransactionManager - local ADO.NET based transactions

  • ServiceDomainPlatformTransactionManager - distributed transaction manager from Enterprise Services

  • TxScopePlatformTransactionManager - local/distributed transaction manager from System.Transactions.

  • HibernatePlatformTransactionManager - local transaction manager for use with NHibernate or mixed ADO.NET/NHibernate data access operations.

Under the covers, the following API calls are made. For the AdoPlatformTransactionManager, Transaction.Begin(), Commit(), Rollback(). ServiceDomainPlatformTransactionManager uses the 'Services without Components' update so that your objects do not need to inherit from ServicedComponent or directly call the Enterprise Services API ServiceDomain.Enter(), Leave; ContextUtil.SetAbort(). TxScopePlatformTransactionManager calls; new TransactionScope(); .Complete(), Dispose(), Transaction.Current.Rollback(). Configuration properties for each transaction manager are specific to the data access technology used. Refer to the API docs for comprehensive information but the examples should give you a good basis for getting started. The HibernatePlatformTransactionManager is described more in the following section .

The GetTransaction(..) method returns a ITransactionStatus object, depending on a ITransactionDefinition parameters. The returned ITransactionStatus might represent a new or existing transaction (if there was a matching transaction in the current call stack - with the implication being that a ITransactionStatus is associated with a logical thread of execution.

The ITransactionDefinition interface specified

  • Isolation: the degree of isolation this transaction has from the work of other transactions. For example, can this transaction see uncommitted writes from other transactions?

  • Propagation: normally all code executed within a transaction scope will run in that transaction. However, there are several options specifying behavior if a transactional method is executed when a transaction context already exists: for example, simply continue running in the existing transaction (the common case); or suspending the existing transaction and creating a new transaction.

  • Timeout: how long this transaction may run before timing out (and automatically being rolled back by the underlying transaction infrastructure).

  • Read-only status: a read-only transaction does not modify any data. Read-only transactions can be a useful optimization in some cases (such as when using NHibernate).

These settings reflect standard transactional concepts. If necessary, please refer to a resource discussing transaction isolation levels and other core transaction concepts because understanding such core concepts is essential to using the Spring Framework or indeed any other transaction management solution.

The ITransactionStatus interface provides a simple way for transactional code to control transaction execution and query transaction status.

Regardless of whether you opt for declarative or programmatic transaction management in Spring, defining the correct IPlatformTransactionManager implementation is absolutely essential. In good Spring fashion, this important definition typically is made using via Dependency Injection.

IPlatformTransactionManager implementations normally require knowledge of the environment in which they work, ADO.NET, NHibernate, etc. The following example shows how a standard ADO.NET based IPlatformTransactionManager can be defined.

We must define a Spring IDbProvider and then use Spring's AdoPlatformTransactionManager, giving it a reference to the IDbProvider. For more information on the IDbProvider abstraction refer to the next chapter.

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

    <db:provider id="DbProvider" 
                provider="SqlServer-1.1" 
                connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/>

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

    . . . other object definitions . . .

</objects>
 

We can also use a transaction manager based on System.Transactions just as easily, as shown in the following example

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

Similarly for the HibernateTransactionManager as shown in the section on ORM transaction management.

Note that in all these cases, application code will not need to change at all since, dependency injection is a perfect companion to using the strategy pattern. We can now change how transactions are managed merely by changing configuration, even if that change means moving from local to global transactions or vice versa.

17.4. Resource synchronization with transactions

How does application code participate with the resources (i.e. Connection/Transactions/Sessions) that are created/reused/cleanedup via the different transaction managers? There are two approaches - a high-level and a low-level approach

17.4.1. High-level approach

The preferred approach is to use Spring's high level persistence integration APIs. These do not replace native APIs, but internally handle resource creation/reuse, cleanup, and optional transaction synchronization (i.e. event notification) of the resources and exception mapping so that user data access code doesn't have to worry about these concerns at all, but can concentrate purely on non-boilerplate persistence logic. Generally, the same inversion of control approach is used for all persistence APIs. In this approach the API has a callback method or delegate that presents the user code with the relevant resource ready to use - i.e. a DbCommand with its Connection and Transaction properties set based on the transaction option metadata. These classes go by the naming scheme 'template', examples of which are AdoTemplate and HibernateTemplate. Convenient 'one-liner' helper methods in these template classes build upon the core callback/IoC design by providing specific implementations of the callback interface.

17.4.2. Low-level approach

A utility class can be used to directly obtain a connection/transaction pair that is aware of the transactional calling context and returns a pair suitable for that context. The class ConnectionUtils contains the static method ConnectionTxPair GetConnectionTxPair(IDbProvider provider) which serves this purpose.

public class LowLevelIntegration
{
  // Spring's IDbProvider abstraction 
  private IDbProvider dbProvider;

  public IDbProvider dbProvider  
  {
    set { dbProvider = value; }
  }
  
  public void DoWork() {
    ConnectionTxPair connTxPair = ConnectionUtils.GetConnectionTxPair(dbProvider);
    //Use some data access library that allows you to pass in the transaction

    DbWrapper dbWrapper = new DbWrapper();
    string cmdText = ... // some command text
    dbWrapper.ExecuteNonQuery(cmdText, connTxPair.Transaction);
  }
}

17.5. Declarative transaction management

Most Spring users choose declarative transaction management. It is the option with the least impact on application code, and hence is most consistent with the ideals of a non-invasive lightweight container.

Spring's declarative transaction management is made possible with Spring's aspect-oriented programming (AOP), although, as the transactional aspects code comes with Spring and may be used in a boilerplate fashion, AOP concepts do not generally have to be understood to make effective use of this code.

The approach is to specify transaction behavior (or lack of it) down to the individual method level. It is also possible to mark a transaction for rollback by calling the SetRollbackOnly() method within a transaction context if necessary. Some of the highlights of Spring's declarative transaction management are:

  • Declarative Transaction management works in any environment. It can work with ADO.NET, System.Transactions, NHibernate etc, with configuration changes only.

  • Enables declarative transaction management to be applied to any class, not merely special classes such as those that inherit from ServicedComponent or other infrastructure related base classes.

  • Declarative rollback rules. Rollback rules can be control declaratively and allow for only specified exceptions thrown within a transactional context to trigger a rollback

  • Spring gives you an opportunity to customize transactional behavior, using AOP. For example if you want to insert custom behavior in the case of a transaction rollback, you can. You can also add arbitrary advice, along with the transactional advice.

  • Spring does not support propagation of transaction context across remote calls.

The concept of rollback rules is important: they enable us to specify which exceptions should cause automatic roll back. We specify this declaratively, in configuration, not in code. So, although you can still call SetRollbackOnly() on the ITransactionStatus object to roll the current transaction back, most often you can specify a rule that MyApplicationException must always result in rollback. This has the significant advantage that business objects do not depend on the transaction infrastructure. For example, they typically don't need to import any Spring transaction APIs or other Spring APIs. However, to rollback the transaction programmatically when using declarative transaction management, use the utility method

TransactionInterceptor.CurrentTransactionStatus.SetRollbackOnly();
[Note]Note

Prior to Spring.NET 1.2 RC1 the API call would be TransactionInterceptor.CurrentTransactionStatus.RollbackOnly = true;

17.5.1. Understanding Spring's declarative transaction implementation

It is not sufficient to tell you simply to annotate your classes with the [Transaction] attribute, add the line (<tx:attribute-driven/>) to your configuration, and then expect you to understand how it all works. This section explains the inner workings of the Spring Framework's declarative transaction infrastructure in the event of transaction-related issues.

The most important concepts to grasp with regard to the Spring Framework's declarative transaction support are that this support is enabled via AOP proxies, and that the transactional advice is driven by metadata (currently XML- or attribute-based). The combination of AOP with transactional metadata yields an AOP proxy that uses a TransactionInterceptor in conjunction with an appropriate IPlatformTransactionManager implementation to drive transactions around method invocations.

Conceptually, calling a method on a transactional proxy looks like this.

17.5.2. Example of declarative transaction implementation

Consider the following interface. The intent is to convey the concepts to you so you can concentrate on the transaction usage and not have to worry about domain specific details.

[Note]Note

A QuickStart application for declarative transaction management is included in the Spring.NET distribution and is decribed here.

The ITestObjectManager is a poor-mans business service layer - the implementation of which will make two DAO calls. Clearly this example is overly simplistic from the service layer perspective as there isn't any business logic at all!. The 'service' interface is shown below.

public interface ITestObjectManager 
{
  void SaveTwoTestObjects(TestObject to1, TestObject to2);

  void DeleteTwoTestObjects(string name1, string name2);
}

The implementation of ITestObjectManager is shown below

public class TestObjectManager : ITestObjectManager 
{

   // Fields/Properties ommited

   [Transaction]
   public void SaveTwoTestObjects(TestObject to1, TestObject to2)
   {
     TestObjectDao.Create(to1.Name, to1.Age);
     TestObjectDao.Create(to2.Name, to1.Age);
   }

   [Transaction]
   public void DeleteTwoTestObjects(string name1, string name2)
   {
     TestObjectDao.Delete(name1);
     TestObjectDao.Delete(name2);
   }
}

Note the Transaction attribute on the methods. Other options such as isolation level can also be specified but in this example the default settings are used. However, please note that the mere presence of the Transaction attribute is not enough to actually turn on the transactional behavior - the Transaction attribute is simply metadata that can be consumed by something that is Transaction attribute-aware and that can use the said metadata to configure the appropriate objects with transactional behavior.

The TestObjectDao property has basic create update delete and find method for the 'domain' object TestObject. TestObject in turn has simple properties like name and age.

public interface ITestObjectDao
{
  void Create(string name, int age);
  void Update(TestObject to);
  void Delete(string name);
  TestObject FindByName(string name);
  IList FindAll();
}

The Create and Delete method implementation is shown below. Note that this uses the AdoTemplate class discussed in the following chapter. Refer to Section 17.4, “Resource synchronization with transactions” for information on the interaction between Spring's high level persistence integration APIs and transaction management features.

public class TestObjectDao : AdoDaoSupport, ITestObjectDao
{
   public void Create(string name, int age)
   {
       AdoTemplate.ExecuteNonQuery(CommandType.Text,
           String.Format("insert into TestObjects(Age, Name) VALUES ({0}, '{1}')", 
           age, name));
   }

   public void Delete(string name)
   {
       AdoTemplate.ExecuteNonQuery(CommandType.Text,
           String.Format("delete from TestObjects where Name = '{0}'",
           name));
   }
}

The TestObjectManager is configured with the DAO objects by standard dependency injection techniques. The client code, which in this case directly asks the Spring IoC container for an instance of ITestObjectManager, will receive a transaction proxy with transaction options based on the attribute metadata. Note that typically the ITestObjectManager would be set on yet another higher level object via dependency injection, for example a web service.

The client calling code is shown below

IApplicationContext ctx =
   new XmlApplicationContext("assembly://Spring.Data.Integration.Tests/Spring.Data/autoDeclarativeServices.xml");

ITestObjectManager mgr = ctx["testObjectManager"] as ITestObjectManager; 

TestObject to1 = new TestObject();
to1.Name = "Jack";
to1.Age = 7;

TestObject to2 = new TestObject();
to2.Name = "Jill";
to2.Age = 8;

mgr.SaveTwoTestObjects(to1, to2);

mgr.DeleteTwoTestObjects("Jack", "Jill");

            

The configuration of the object definitions of the DAO and manager classes is shown below.

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

    <db:provider id="DbProvider" 
                  provider="SqlServer-1.1" 
                  connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/>

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

    <object id="testObjectDao" type="Spring.Data.TestObjectDao, Spring.Data.Integration.Tests">
        <property name="AdoTemplate" ref="adoTemplate"/>
    </object>
            
    
    <!-- The object that performs multiple data access operations -->
    <object id="testObjectManager" type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests">
        <property name="TestObjectDao" ref="testObjectDao"/>
    </object>
 
    
</objects>

This is standard Spring configuration and as such provides you with the flexibility to parameterize your connection string and to easily switch implementations of your DAO objects.

The following section shows how to configure the declarative transactions using Spring's transaction namespace.

17.5.3. Declarative transactions using the transaction namespace

Spring provides a custom XML schema to simplify the configuration of declarative transaction management. If you would like to perform attribute driven transaction management you first need to register the custom namespace parser for the transaction namespace. This can be done in the application configuration file as shown below

<?xml version="1.0" encoding="utf-8" ?>
<configuration>

  <configSections>

    <sectionGroup name="spring">
      <section name="parsers" type="Spring.Context.Support.NamespaceParsersSectionHandler, Spring.Core" />
      
      <!-- other spring config sections like context, typeAliases, etc not shown for brevity -->

    </sectionGroup>
  </configSections>

  <spring>
    
    <parsers>
      <parser type="Spring.Data.Config.DatabaseNamespaceParser, Spring.Data" />
      <parser type="Spring.Transaction.Config.TxNamespaceParser, Spring.Data" />
      <parser type="Spring.Aop.Config.AopNamespaceParser, Spring.Aop" />
    </parsers>

   </spring>

  </configSections>

Instead of using the XML configuration listed at the end of the previous section (declarativeServices.xml you can use the following. Note that the schemaLocation in the objects element is needed only if you have not installed Spring's schema into the proper VS.NET 2005 location. See the chapter on VS.NET integration for more details.

<objects xmlns="http://www.springframework.net"
	        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns:tx="http://www.springframework.net/tx"
         xmlns:db="http://www.springframework.net/database"
         xsi:schemaLocation="http://www.springframework.net http://www.springframework.net/schema/objects/spring-objects.xsd
         http://www.springframework.net/schema/tx http://www.springframework.net/schema/tx/spring-tx-1.1.xsd"
         http://www.springframework.net/schema/db http://www.springframework.net/schema/db/spring-database.xsd"> 

    <db:provider id="DbProvider" 
                  provider="SqlServer-1.1" 
                  connectionString="Data Source=(local);Database=Spring;User ID=springqa;Password=springqa;Trusted_Connection=False"/>

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

    <object id="testObjectDao" type="Spring.Data.TestObjectDao, Spring.Data.Integration.Tests">
        <property name="AdoTemplate" ref="adoTemplate"/>
    </object>
            
    
    <!-- The object that performs multiple data access operations -->
    <object id="testObjectManager" 
            type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests">
        <property name="TestObjectDao" ref="testObjectDao"/>
    </object>

   
    <tx:attribute-driven transaction-manager="transactionManager"/>

</objects>
[Tip]Tip

You can actually omit the 'transaction-manager' attribute in the <tx:attribute-driven/> tag if the object name of the IPlatformTransactionManager that you want to wire in has the name 'transactionManager'. If the PlatformTransactionManager object that you want to dependency inject has any other name, then you have to be explicit and use the 'transaction-manager' attribute as in the example above.

The various optional elements of the <tx:attribute-driven/> tag are summarised in the following table

Table 17.1. <tx:annotation-driven/> settings
AttributeRequired?DefaultDescription
transaction-manager NotransactionManager

The name of transaction manager to use. Only required if the name of the transaction manager is not transactionManager, as in the example above.

proxy-target-type No 

Controls what type of transactional proxies are created for classes annotated with the [Transaction] attribute. If "proxy-target-type" attribute is set to "true", then class-based proxies will be created (proxy inherits from target class, however calls are still delegated to target object via composition. This allows for casting to base class. If "proxy-target-type" is "false" or if the attribute is omitted, then a pure composition based proxy is created and you can only cast the proxy to implemented interfaces. (See the section entitled Section 13.6, “Proxying mechanisms” for a detailed examination of the different proxy types.)

order No 

Defines the order of the transaction advice that will be applied to objects annotated with [Transaction]. More on the rules related to ordering of AOP advice can be found in the AOP chapter (see section Section 13.3.2.5, “Advice Ordering”). Note that not specifying any ordering will leave the decision as to what order advice is run in to the AOP subsystem.


[Note]Note

The "proxy-target-type" attribute on the <tx:attribute-driven/> element controls what type of transactional proxies are created for classes annotated with the Transaction attribute. If "proxy-target-type" attribute is set to "true", then inheritance-based proxies will be created. If "proxy-target-type" is "false" or if the attribute is omitted, then composition based proxies will be created. (See the section entitled Section 13.6, “Proxying mechanisms” for a detailed examination of the different proxy types.)

You can also define the transactional semantics you want to apply through the use of a <tx:advice> definition. This lets you define the transaction metadata such as propagation and isolation level as well as the methods for which that metadata applies external to the code unlike the case of using the transaction attribute. The <tx:advice> definition creates an instance of a ITransactionAttributeSource during parsing time. Switching to use <tx:advice> instead of <tx:attribute-driven/> in the example would look like the following

<tx:advice id="txAdvice" transaction-manager="transactionManager">
  <tx:attributes>
    <tx:method name="Save*"/>   
    <tx:method name="Delete*"/>  
  </tx:attributes>
</tx:advice>

This says that all methods that start with Save and Delete would have associated with them the default settings of transaction metadata. These default values are listed below..

Here is an example using other elements of the <tx:method/> definition

  <!-- the transactional advice (i.e. what 'happens'; see the <aop:advisor/> object below) -->
  <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!-- the transactional semantics... -->
    <tx:attributes>
      <!-- all methods starting with 'get' are read-only -->
      <tx:method name="Get*" read-only="true"/>
      <!-- other methods use the default transaction settings (see below) -->
      <tx:method name="*"/>
    </tx:attributes>
  </tx:advice>

The <tx:advice/> definition reads as “... all methods on starting with 'Get' are to execute in the context of a read-only transaction, and all other methods are to execute with the default transaction semantics”. The 'transaction-manager' attribute of the <tx:advice/> tag is set to the name of the PlatformTransactionManager object that is going to actually drive the transactions (in this case the 'transactionManager' object).

You can also use the AOP namespace <aop:advisor> element to tie together a pointcut and the above defined advice as shown below.

<object id="serviceOperation" type="Spring.Aop.Support.SdkRegularExpressionMethodPointcut, Spring.Aop">
    <property name="pattern" value="Spring.TxQuickStart.Services.*"/>
</object>

<aop:config>
     
    <aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>

</aop:config>

This is assuming that the service layer class, TestObjectManager, in the namespace Spring.TxQuickStart.Services. The <aop:config/> definition ensures that the transactional advice defined by the 'txAdvice' object actually executes at the appropriate points in the program. First we define a pointcut that matches any operation defined on classes in the Spring.TxQuickStart.Services (you can be more selective in your regular expression). Then we associate the pointcut with the 'txAdvice' using an advisor. In the example, the result indicates that at the execution of a 'SaveTwoTestObjects' and 'DeleteTwoTestObject', the advice defined by 'txAdvice' will be run.

The various transactional settings that can be specified using the <tx:advice/> tag. The default <tx:advice/> settings are listed below and are the same as when you use the Transaction attribute.

  • The propagation setting is TransactionPropagation.Required

  • The isolation level is IsolationLevel.ReadCommitted

  • The transaction is read/write

  • The transaction timeout defaults to the default timeout of the underlying transaction system, or none if timeouts are not supported

  • EnterpriseServicesInteropOption (.NET 2.0 only with TxScopeTransactionManager) - options between transaction created with System.Transactions and transactions created through COM+

  • Any exception will trigger rollback.

These default settings can be changed; the various attributes of the <tx:method/> tags that are nested within <tx:advice/> and <tx:attributes/> tags are summarized below:

Table 17.2. <tx:method/> settings
AttributeRequired?DefaultDescription
name Yes 

The method name(s) with which the transaction attributes are to be associated. The wildcard (*) character can be used to associate the same transaction attribute settings with a number of methods; for example, 'Get*', 'Handle*', 'On*Event', and so forth.

propagation NoRequiredThe transaction propagation behavior
isolation NoReadCommittedThe transaction isolation level
timeout No-1The transaction timeout value (in seconds)
read-only NofalseIs this transaction read-only?
EnterpriseServicesInteropOption NoNoneInteroperability options with COM+ transactions. (.NET 2.0 and TxScopeTransactionManager only)
rollback-for No 

The Exception(s) that will trigger rollback; comma-delimited. For example, 'MyProduct.MyBusinessException,ValidationException'

no-rollback-for No 

The Exception(s) that will not trigger rollback; comma-delimited. For example, 'MyProduct.MyBusinessException,ValidationException'


17.5.4. Transaction attribute settings

The Transaction attribute is metadata that specifies that a class or method must have transactional semantics. The default Transaction attribute settings are

  • The propagation setting is TransactionPropagation.Required

  • The isolation level is IsolationLevel.ReadCommitted

  • The transaction is read/write

  • The transaction timeout defaults to the default timeout of the underlying transaction system, or none if timeouts are not supported

  • EnterpriseServicesInteropOption (.NET 2.0 only with TxScopeTransactionManager) - options between transaction created with System.Transactions and transactions created through COM+

  • Any exception will trigger rollback.

The default settings can, of course, be changed; the various properties of the Transaction attribute are summarised in the following table

Table 17.3. Transaction attribute properties
Property Type Description
TransactionPropagationenumeration, Spring.Transaction.TransactionPropagationoptional propagation setting. Required, Supports, Mandatory, RequiresNew, NotSupported, Never, Nested
Isolation System.Data.IsolationLevel optional isolation level
ReadOnlyboolean
read/write vs. read-only transaction
EnterpriseServicesInteropOptionenumeration System.Transactions.EnterpriseServicesInteropOptionOptions for interoperability with COM+ transactions (.NET 2.0 and TxScopeTransactionManager only)
Timeoutint (in seconds granularity)the transaction timeout
RollbackForan array of Type objectsan optional array of exception classes that must cause rollback
NoRollbackForan array of Type objectsan optional array of exception classes that must not cause rollback

Note that setting the TransactionPropagation to Nested will throw a NestedTransactionNotSupportedException in a case where an actual nested transaction occurs, i.e. not in the case of applying the Nested propagation but in fact no nested calls are made. This will be fixed for the Spring 1.2 release for SqlServer and Oracle which support nested transactions. Also note, that changing of isolation levels on a per-method basis is also scheduled for the Spring 1.2 release since it requires detailed command text metadata for each dbprovider. Please check the forums for news on when this feature will be introduced into the nightly builds.

If you specify an exception type for 'NoRollbackFor' the action taken is to commit the work that has been done in the database up to the point where the exception occurred. The exception is still propagated out to the calling code.

The ReadOnly boolean is a hint to the data access technology to enable read-only optimizations. This currently has no effect in Spring's ADO.NET framework. If you would like to enable read-only optimizations in ADO.NET this is generally done via the 'Mode=Read' or 'Mode=Read-Only" options in the connection string. Check your database provider for more information. In the case of NHibernate the flush mode is set to Never when a new Session is created for the transaction.

Throwing exceptions to indicate failure and assuming success is an easier and less invasive programming model than performing the same task Programatically - ContextUtil.MyTransactionVote or TransactionScope.Complete. The rollback options are a means to influence the outcome of the transaction based on the exception type which adds an extra degree of flexibility.

Having any exception trigger a rollback has similar behavior as applying the AutoComplete attribute available when using .NET Enterprise Services. The difference with AutoComplete is that using AutoComplete is also coupled to the lifetime of the ServicedComponent since it sets ContextUtil.DeactivateOnReturn to true. For a stateless DAO layer this is not an issue but it could be in other scenarios. Spring's transactional aspect does not affect the lifetime of your object.

17.5.5. Declarative Transactions using AutoProxy

if you choose not to use the transaction namespace for declarative transaction management then you can use 'lower level' object definitions to configure declarative transactions. The use of Spring's autoproxy functionality defines criteria to select a collection of objects to create a transactional AOP proxy. There are two AutoProxy classes that you can use, ObjectNameAutoProxyCreator and DefaultAdvisorAutoProxyCreator. If you are using the new transaction namespace support you do not need to configure these objects as a DefaultAdvisorAutoProxyCreator is created 'under the covers' while parsing the transaction namespace elements

17.5.5.1. Creating transactional proxies with ObjectNameAutoProxyCreator

The ObjectNameAutoProxyCreator is useful when you would like to create transactional proxies for many objects. The definitions for the TransactionInterceptor and associated attributes is done once. When you add new objects to your configuration file that need to be proxies you only need to add them to the list of object referenced in the ObjectNameAutoProxyCreator. Here is an example showing its use. Look in the section that use ProxyFactoryObject for the declaration of the transactionInterceptor.

    <object name="autoProxyCreator"
            type="Spring.Aop.Framework.AutoProxy.ObjectNameAutoProxyCreator, Spring.Aop">
            
        <property name="InterceptorNames" value="transactionInterceptor"/>
        <property name="ObjectNames">
            <list>
                <idref local="testObjectManager"/>
            </list>
        </property>
    </object>

17.5.5.2. Creating transactional proxies with DefaultAdvisorAutoProxyCreator

This is not longer a common way to configure declarative transactions but is discussed in the "Classic Spring" appendiex here.

17.6. Programmatic transaction management

Spring provides two means of programmatic transaction management:

  • Using the TransactionTemplate

  • Using a IPlatformTransactionManager implementation directly

These are located in the Spring.Transaction.Support namespace. If you are going to use programmatic transaction management, the Spring team generally recommends the first approach (i.e. Using the TransactionTemplate)

17.6.1. Using the TransactionTemplate

The TransactionTemplate adopts the same approach as other Spring templates such as AdoTemplate and HibernateTemplate. It uses a callback approach, to free application code from having to do the boilerplate acquisition and release of resources, and results in code that is intention driven, in that the code that is written focuses solely on what the developer wants to do. Granted that the using construct of System.Transaction alleviates much of this. One key difference with the approach taken with the TransactionTemplate is that a commit is assumed - throwing an exception triggers a rollback instead of using the TransactionScope API to commit or rollback. This also allows for the use of rollback rules, that is a commit can still occur for exceptions of certain types.

[Note]Note

As you will immediately see in the examples that follow, using the TransactionTemplate absolutely couples you to Spring's transaction infrastructure and APIs. Whether or not programmatic transaction management is suitable for your development needs is a decision that you will have to make yourself.

Application code that must execute in a transaction context looks like this. You, as an application developer, will write a ITransactionCallback implementation (typically expressed as an anonymous delegate) that will contain all of the code that you need to have execute in the context of a transaction. You will then pass an instance of your custom ITransactionCallback to the Execute(..) method exposed on the TransactionTemplate. Note that the ITransactionCallback can be used to return a value:

public class SimpleService : IService
{
  private TransactionTemplate transactionTemplate;

  public SimpleService(IPlatformTransactionManager transactionManager)
  {
     AssertUtils.ArgumentNotNull(transactionManager, "transactionManager");
     transactionTemplate = new TransactionTemplate(transactionManager);
  }

  public object SomeServiceMethod()
  {
     return tt.Execute(delegate {
                             UpdateOperation(userId);
                             return ResultOfUpdateOperation2();
                       });
  }
}

This code example is specific to .NET 2.0 since it uses anonymous delegates, which provides a particularly elegant means to invoke a callback function as local variables can be referred to inside the delegate, i.e. userId. In this case the ITransactionStatus was not exposed in the delegate (delegate can infer the signature to use), but one could also obtain a reference to the ITransactionStatus instance and set the RollbackOnly property to trigger a rollback - or alternatively throw an exception. This is shown below

tt.Execute(delegate(ITransactionStatus status)
           {
             try {
               UpdateOperation1();
               UpdateOperation2();
             } catch (SomeBusinessException ex) {
               status.RollbackOnly = true;
             }           
             return null;
           });

If you are using .NET 1.1 then you should provide a normal delegate reference or an instance of a class that implements the ITransactionCallback interface. This is shown below

tt.Execute(new TransactionRollbackTxCallback(amount));


    public class TransactionRollbackTxCallback : ITransactionCallback
    {
        private decimal amount;

        public TransactionRollbackTxCallback(decimal amount)
        {
            this.amount = amount
        }

        public object DoInTransaction(ITransactionStatus status)
        {
             adoTemplate.ExecuteNonQuery(CommandType.Text, "insert into dbo.Debits (DebitAmount) VALUES (@amount)", "amount", DbType.Decimal, 0,555);
             // decide you need to rollback...
             status.RollbackOnly = true;
             return null;
        }
    }

Application classes wishing to use the TransactionTemplate must have access to a IPlatformTransactionManager (which will typically be supplied to the class via dependency injection). It is easy to unit test such classes with a mock or stub IPlatformTransactionManager.

17.6.1.1. Specifying transaction settings

Transaction settings such as the propagation mode, the isolation level, the timeout, and so forth can be set on the TransactionTemplate either programmatically or in configuration. TransactionTemplate instances by default have the default transactional settings. Find below an example of programmatically customizing the transactional settings for a specific TransactionTemplate.

public class SimpleService : IService
{
  private TransactionTemplate transactionTemplate;

  public SimpleService(IPlatformTransactionManager transactionManager)
  {
     AssertUtils.ArgumentNotNull(transactionManager, "transactionManager");
     transactionTemplate = new TransactionTemplate(transactionManager);

     // the transaction settings can be set here explicitly if so desired

     transactionTemplate.TransactionIsolationLevel = IsolationLevel.ReadUncommitted;
     transactionTemplate.TransactionTimeout = 30;

     // and so forth...
  }

  . . .

}

Find below an example of defining a TransactionTemplate with some custom transactional settings, using Spring XML configuration. The 'sharedTransactionTemplate' can then be injected into as many services as are required.

<object id="sharedTransactionTemplate"
        type="Spring.Transaction.Support.TransactionTemplate, Spring.Data">
  <property name="TransactionIsolationLevel" value="IsolationLevel.ReadUncommitted"/>
  <property name="TransactionTimeout" value="30"/>
</object>

Finally, instances of the TransactionTemplate class are threadsafe, in that instances do not maintain any conversational state. TransactionTemplate instances do however maintain configuration state, so while a number of classes may choose to share a single instance of a TransactionTemplate, if a class needed to use a TransactionTemplate with different settings (for example, a different isolation level), then two distinct TransactionTemplate instances would need to be created and used.

17.6.2. Using the PlatformTransactionManager

You can also use the PlatformTransactionManager directly to manage your transaction. Simply pass the implementation of the PlatformTransactionManager you're using to your object via a object reference through standard Dependency Injection techniques. Then, using the TransactionDefinition and ITransactionStatus objects, you can initiate transactions, rollback and commit.

DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.PropagationBehavior = TransactionPropagation.Required;

ITransactionStatus status = transactionManager.GetTransaction(def);

try
{
  // execute your business logic here
} catch (Exception e)
{
  transactionManager.Rollback(status);
  throw;
}
transactionManager.Commit(status);

Note that a corresponding 'using TransactionManagerScope' class can be modeled to get similar API usage to System.Transactions TransactionScope.

17.7. Choosing between programmatic and declarative transaction management

Programmatic transaction management is usually a good idea only if you have a small number of transactional operations. For example, if you have a web application that require transactions only for certain update operations, you may not want to set up transactional proxies using Spring or any other technology. In this case, using the TransactionTemplate may be a good approach. On the other hand, if your application has numerous transactional operations, declarative transaction management is usually worthwhile. It keeps transaction management out of business logic, and is not difficult to configure in Spring.

17.8. Transaction lifecycle and status information

You can query the status of the current Spring managed transaction with the class TransactionSynchronizationManager. Typical application code should not need to rely on using this class but in some cases it is convenient to receive events around the lifecycle of the transaction, i.e. before committing, after committing. TransactionSynchronizationManager provides a method to register a callback object that is informed on all significant stages in the transaction lifecycle. Note that you can register for lifecycle call back information for any of the transaction managers you use, be it NHibernate or local ADO.NET transactions.

The method to register a callback with the TransactionSynchronizationManager is

public static void RegisterSynchronization( ITransactionSynchronization synchronization ) 

Please refer to the SDK docs for information on other methods in this class.

The ITransactionSynchronization interface is

public interface ITransactionSynchronization
{

  // Typically used by Spring resource management code
  void Suspend();
  void Resume();

  // Transaction lifeycyle callback methods
  // Typically used by Spring resource management code but maybe useful in certain cases to application code
  void BeforeCommit( bool readOnly );
  void AfterCommit();
  void BeforeCompletion();
  void AfterCompletion( TransactionSynchronizationStatus status );
}

The TransactionSynchronizationStatus is an enum with the values Committed, Rolledback, and Unknown.