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, EnterpriseServices, 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 up 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, EnterpriseServices, 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) which '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 EnterpriseServices solve this problem - and what about the functionality in the System.Transactions namespace? The answer is yes...and no. EnterpriseServices 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 EnterpriseServices 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 you 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 EnterpriseServices 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. EnterpriseServices is not without it small worts 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, EnterpriseServices, 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 programmatically. 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 EnterpriseServices

  • 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 EnterpriseServices 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.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.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 and pair suitable for that context. The class ConnectionUtils contains the static method ConnectionTxPair GetConnectionTxPair(IDbProvider provider) which serves this purpose.

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 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 basic 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 setting the 'RollbackOnly' property on the ITransactionStatus object returned from the IPlatformTransactionManager 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.

Note rollback rules as configured from XML are still under development.

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, while we can still set RollbackOnly on the ITransactionStatus object to roll the current transaction back programmatically, most often we can specify a rule that MyApplicationException must always result in rollback. This has the significant advantage that business objects don't need to depend on the transaction infrastructure. For example, they typically don't need to import any Spring APIs, transaction or other.

17.5.1. Understanding Spring's declarative transaction implementation

The aim of this section is to dispel the mystique that is sometimes associated with the use of declarative transactions. It is all very well for this reference documentation to simply tell you to annotate your classes with the Transaction attribute and add some boilerplate XML to your IoC configuration, and then expect you to understand how it all works. This section will explain the inner workings of Spring's declarative transaction infrastructure to help you navigate your way back upstream to calmer waters in the event of transaction-related issues.

[Note]Note

Looking at the Spring source code is a good way to get a real understanding of Spring's transaction support. You should find the API documentation informative and complete. We suggest turning the logging level to 'DEBUG' in your Spring-enabled application(s) during development to better see what goes on under the hood.

The most important concepts to grasp with regard to Spring'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 a proxy with transactional metadata yields an AOP proxy that uses a TransactionInterceptor in conjunction with an appropriate IPlatformTransactionManager implementation to drive transactions around method invocations.

[Note]Note

Although knowledge of AOP (and specifically Spring AOP) is not required in order to use Spring's declarative transaction support, it can help. Spring AOP is thoroughly covered in the AOP chapter.

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

The flow of events is the following. First the set of objects you would like to apply AOP transactional advice to are identified. There are a variety of ways to configure the Spring IoC container to create proxies for the defined object definitions. The standard Spring AOP based options are

There is also a convenience subclass of ProxyFactoryObject, namely TransactionProxyFactoryObject, that sets some common default values for the specific case of applying transactional advice.

The DefaultAdvisorAutoProxyCreator is very powerful and is the means by which Spring can be configured to use attributes to identify the pointcuts where transaction advice should be applied. The advisor that performs that task is TransactionAttributeSourceAdvisor.

[Note]Note

Note, think of the word 'Attribute' in this class name not as the .NET attribute but as the transaction 'options' you want to specify. This name is inherited from the Java version and the name will be changed in the RC1 release to avoid confusion since a common naming convention when creating classes are .NET attributes is to put the word 'Attribute' in the name.

Which one of the many options available should you choose for your development? That depends, each one has it own set of pro's and con's which will be discussed in turn in the following sections.

With the transactional AOP proxy now created we can discuss the flow of events in the code as proxied methods are invoked. When the method is invoked, before calling the target object's method, a transaction is created if one hasn't already been created. Then the target method is invoked. If there was an exception throw, the transaction is typically rolled back, but it can also be committed if the exception type specified in the transaction option, NoRolbackFor, matches the thrown exception. If no exception was thrown, that is taken as a sign of success and the transaction is committed.

When using other AOP advice with the transactional advice you can set the order of the 'interceptor chain' so that, for example, performance monitoring advice always precede the transactional advice.

17.5.2. A First Example

Consider the following interface. The intent is to convey the concepts to you can concentrate on the transaction usage and not have to worry about domain specific details. The ITestObjectManager is a poor-mans business service layer - the implementation of which wll 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 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.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 configuration to create a transactional proxy for the manager class is shown below.

    <!-- The rest of the config file is common no matter how many objects you add -->
    <!-- that you would like to have declarative tx management applied to -->    
    
    <object id="autoProxyCreator" 
            type="Spring.Aop.Framework.AutoProxy.DefaultAdvisorAutoProxyCreator, Spring.Aop">
    </object>
    
    <object id="transactionAdvisor"
            type="Spring.Transaction.Interceptor.TransactionAttributeSourceAdvisor, Spring.Data">
        <property name="TransactionInterceptor" ref="transactionInterceptor"/>
    </object>
    
    
    <!-- Transaction Interceptor -->
    <object id="transactionInterceptor" 
            type="Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data">
        <property name="TransactionManager" ref="transactionManager"/>
        <property name="TransactionAttributeSource" ref="attributeTransactionAttributeSource"/>        
    </object>
    
    <object id="attributeTransactionAttributeSource"
            type="Spring.Transaction.Interceptor.AttributesTransactionAttributeSource, Spring.Data">    
    </object>

Granted this is a bit verbose and hard to grok at first site - however you only need to grok this once as it is 'boiler plate' XML you can reuse across multiple projects. What these object definitions are doing is to instruct Spring's to look for all objects within the IoC configuration that have the [Transaction] attribute and then apply the AOP transaction interceptor to them based on the transaction options contained in the attribute. The attribute serves both as a pointcut and as the declaration of transactional option information.

Since this XML fragment is not tied to any specific object references it can be included in its own file and then imported via the <import> element. In examples and test code this XML configuraiton fragment is named declarativeServices.xml See ??? for more information.

The classes and their roles in this configuration fragment are listed below

  • TransactionInterceptor is the AOP advice responsible for performing transaction management functionality.

  • TransactionAttributeSourceAdvisor is a AOP Advisor that holds the TransactionInterceptor, which is the advice, and a pointcut (where to apply the advice), in the form of a TransactionAttributeSource.

  • AttributesTransactionAttributeSource is an implementation of the ITransactionAttributeSource interface that defines where to get the transaction metadata defining the transaction semantics (isolation level, propagation behavior, etc) that should be applied to specific methods of specific classes. The transaction metadata is specified via implementations of the ITransactionAttributeSource interface. This example shows the use of the implementation Spring.Transaction.Interceptor.AttributesTransactionAttributeSource to obtain that information from standard .NET attributes. By the very nature of using standard .NET attributes, the attribute serves double duty in identifying the methods where the transaction semantics apply. Alternative implementations of ITransactionAttributeSource available are MatchAlwaysTransactionAttributeSource, NameMatchTransactionAttributeSource, or MethodMapTransactionAttributeSource.

  • DefaultAdvisorAutoProxyCreator: looks for Advisors in the context, and automatically creates proxy objects which are the transactional wrappers

Refer to the following section for a more convenient way to achieve the same goal of declarative transaction management using attributes.

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 section 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 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/schema/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/tx http://www.springframework.net/schema/tx/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.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 element of the <tx:attribute-driven/> tag are summarised in the following table

Table 17.1. <tx:annotation-driven/> settings

AttributeRequired?DefaultDescription
transaction-managerNotransactionManager

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-typeNo 

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.)

orderNo 

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 methos for which that metadata applies external to the code unlike the case of using the transaction attribute. The <tx:advice> definition creates in 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/> bean 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="RegularExpressionPointcut">
    <property name="pattern" value="Spring.TxQuickStart.Services.*"/>
</object>

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

</aop:config>

This is assuming the 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.Unspecified

  • 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
nameYes 

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.

propagationNoRequiredThe transaction propagation behavior
isolationNoUnspecifiedThe transaction isolation level
timeoutNo-1The transaction timeout value (in seconds)
read-onlyNofalseIs this transaction read-only?
EnterpriseServicesInteropOptionNoNoneInteropability options with COM+ transactions. (.NET 2.0 and TxScopeTransactionManager only)
rollback-forNo 

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

no-rollback-forNo 

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.Unspecified

  • 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

PropertyTypeDescription
TransactionPropagationenumeration, Spring.Transaction.TransactionPropagationoptional propagation setting. Required, Supports, Mandatory, RequiresNew, NotSupported, Never, Nested
IsolationSystem.Data.IsolationLeveloptional 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 which 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 when actual nested transations 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 optimizatations. This currenlty 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 programmatically - 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 also is coupled to the lifetime of the ServicedCompent since it sets ContextUtil.DeactivateOnReturn to true. For a stateless DAO layer this is not an issue bit 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. This approach was shown in the first example. 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

The is a commonly used way to configure declarative transactions since it enables you to refer to the transaction attribute as the pointcut to use for the transactional advice for any object definition defined in the IoC container. An example of this configuration approach was shown in Chapter 5.

17.5.6. Declarative Transactions using TransactionProxyFactoryObject

The TransactionProxyFactoryObject is easier to use than a ProxyFactoryObject for most cases since the transaction interceptor and transaction attributes are properties of this object. This removes the need to declare them as seperate objects. Also, unlike the case with the ProxyFactoryObject, you do not have to give fully qualified method names, just the normal 'short' method name. Wild card matchin on the method name is also allowed, which in practice helps to enforce a common naming convention to the methods of your DAOs. The example from chapter 5 is shown here using a TransactionProxyFactoryObject.

    <object id="testObjectManager" 
            type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data">
        
        <property name="PlatformTransactionManager" ref="adoTransactionManager"/>
        <property name="Target">
            <object type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests">
                <property name="TestObjectDao" ref="testObjectDao"/>
            </object>
        </property> 
        <property name="TransactionAttributes">
            <name-values>
                <add key="Save*" value="PROPAGATION_REQUIRED"/>
                <add key="Delete*" value="PROPAGATION_REQUIRED"/>
            </name-values>
        </property>
    </object>
    

Note the use of an inner object definition for the target which will make it impossible to obtain an unproxied reference to the TestObjectManager.

As can be seen in the above definition, the TransactionAttributes property holds a collection name/value pairs. The key of each pair is a method or methods (a * wildcard ending is optional) to apply transactional semantics to. Note that the method name is not qualified with a package name, but rather is considered relative to the class of the target object being wrapped. The value portion of the name/value pair is the TransactionAttribute itself that needs to be applied. When specifying it as a string value as in this example, it's in String format as defined by TransactionAttributeEditor. This format is:

PROPAGATION_NAME,ISOLATION_NAME,readOnly,timeout_NNNN,+Exception1,-Exception2

Note that the only mandatory portion of the string is the propagation setting. The default transactions semantics which apply are as follows:

  • Exception Handling: All exceptions thrown trigger a rollback.

  • Transactions are read/write

  • Isolation Leve: TransactionDefinition.ISOLATION_DEFAULT

  • Timeout: TransactionDefinition.TIMEOUT_DEFAULT

Multiple rollback rules can be specified here, comma-separated. A - prefix forces rollback; a + prefix specifies commit. Under the covers the IDictionary of name value pairs wil be converted to an instance of NameMatchTransactionAttributeSource

The TransactionProxyFactoryObject allows you to set optional "pre" and "post" advice, for additional interception behavior, using the "PreInterceptors" and "PostInterceptors" properties. Any number of pre and post advices can be set, and their type may be Advisor (in which case they can contain a pointcut), MethodInterceptor or any advice type supported by the current Spring configuration (such as ThrowsAdvice, AfterReturningtAdvice or BeforeAdvice, which are supported by default.) These advices must support a shared-instance model. If you need transactional proxying with advanced AOP features such as stateful mixins, it's normally best to use the generic ProxyFactoryObject, rather than the TransactionProxyFactoryObject convenience proxy creator.

17.5.7. Concise proxy definitions

Using abstract object definitions in conjunction with a TransactionProxyFactoryObject provides you a more concise means to reuse common configuration information instead of duplicating it over and over again with a definition of a TransactionProxyFactoryObject per object. Objects that are to be proxied typically have the same pattern of method names, Save*, Find*, etc. This commonality can be placed in an abstract object definition, which other object definitions refer to and change only the configuration information which is different. An abstract object definition is shown below

   <object id="txProxyTemplate" abstract="true"
            type="Spring.Transaction.Interceptor.TransactionProxyFactoryObject, Spring.Data">
        
        <property name="PlatformTransactionManager" ref="adoTransactionManager"/>

        <property name="TransactionAttributes">
            <name-values>
                <add key="Save*" value="PROPAGATION_REQUIRED"/>
                <add key="Delete*" value="PROPAGATION_REQUIRED"/>
            </name-values>
        </property>
    </object>

Subsequent definitions can refer to this 'base' configuration as shown below

<object id="testObjectManager" parent="txProxyTemplate">
    <property name="Target">
            <object type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests">
                <property name="TestObjectDao" ref="testObjectDao"/>
            </object>
    </property> 
</object>

17.5.8. Declarative Transactions using ProxyFactoryObject

Using the general ProxyFactoryObject to declar transactions gives you a great deal of control over the proxy created since you can specify additional advice, such as for logging or performance. Based on the the example shown previously a sample configuration using ProxyFactoryObject is shown below

    <object id="testObjectManagerTarget" type="Spring.Data.TestObjectManager, Spring.Data.Integration.Tests">
        <property name="TestObjectDao" ref="testObjectDao"/>
    </object>

    <object id="testObjectManager" type="Spring.Aop.Framework.ProxyFactoryObject, Spring.Aop">
        
        <property name="Target" ref="testObjectManagerTarget"/>
        <property name="ProxyInterfaces">
            <value>Spring.Data.ITestObjectManager</value>
        </property>
        <property name="InterceptorNames">
            <value>transactionInterceptor</value>
        </property>      
        
    </object>

The ProxyFactoryObject will create a proxy for the Target, i.e. a TestObjectManager instance. An inner object definition could also have been used such that it would make it impossible for to obtain an un proxied object from the container. The interceptor name refers to the following definition.

    <object id="transactionInterceptor" type="Spring.Transaction.Interceptor.TransactionInterceptor, Spring.Data">

        <property name="TransactionManager" ref="adoTransactionManager"/>

        <!-- note do not have converter from string to this property type registered -->
        <property name="TransactionAttributeSource" ref="methodMapTransactionAttributeSource"/>        
    </object>
    
    <object name="methodMapTransactionAttributeSource" 
            type="Spring.Transaction.Interceptor.MethodMapTransactionAttributeSource, Spring.Data">
        <property name="MethodMap">
           <dictionary>           
              <entry key="Spring.Data.TestObjectManager.SaveTwoTestObjects, Spring.Data.Integration.Tests" 
                     value="PROPAGATION_REQUIRED"/>
              <entry key="Spring.Data.TestObjectManager.DeleteTwoTestObjects, Spring.Data.Integration.Tests" 
                     value="PROPAGATION_REQUIRED"/>                     
           </dictionary>        
        </property>     
    </object>

The transaction options for each method are specified using a dictionary containing the class name + method name, assembly as the key and the value is of the form

  • <Propagation Behavior>, <Isolation Level>, <ReadOnly>, -Exception, +Exception

All but the propagation behavior are optional. The + and - are used in front of the name of an exception. Minus indicates to rollback if the exception is thrown, the Plus indicates to commit if the exception is thrown.

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. sIf 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. 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.

Like many of the other template classes in Spring, a TransactionTemplate instance is threadsafe.

Application code that must execute in a transaction context looks like this. Note that the ITransactionCallback can be used to return a value:

TransactionTemplate tt = new TransactionTemplate(TransactionManager);

string userId = "Stewie";

object result = tt.Execute(delegate {
                             dao.UpdateOperation(userId);
                             return dao.UpdateOperation2();
                           });

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)
           {
             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;
           });

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.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.