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.
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.
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.
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
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.
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); } }
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 | |
---|---|
Prior to Spring.NET 1.2 RC1 the API call would be
|
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.
Note | |
---|---|
Spring AOP is covered in Chapter 13, Aspect Oriented Programming with Spring.NET |
Conceptually, calling a method on a transactional proxy looks like this.
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 | |
---|---|
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.
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 | |
---|---|
You can actually omit the
|
The various optional elements of the <tx:attribute-driven/> tag are summarised in the following table
<tx:annotation-driven/>
settingsAttribute | Required? | Default | Description |
---|---|---|---|
transaction-manager
| No | transactionManager |
The name of transaction manager to use. Only required
if the name of the transaction manager is not
|
proxy-target-type
| No |
Controls what type of transactional proxies are
created for classes annotated with the
| |
order
| No |
Defines the order of the transaction advice that will
be applied to objects annotated with
|
Note | |
---|---|
The " |
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:
<tx:method/>
settingsAttribute | Required? | Default | Description |
---|---|---|---|
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,
| |
propagation
| No | Required | The transaction propagation behavior |
isolation
| No | ReadCommitted | The transaction isolation level |
timeout
| No | -1 | The transaction timeout value (in seconds) |
read-only
| No | false | Is this transaction read-only? |
EnterpriseServicesInteropOption
| No | None | Interoperability options with COM+ transactions. (.NET 2.0 and TxScopeTransactionManager only) |
rollback-for
| No |
The | |
no-rollback-for
| No |
The |
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
Property | Type | Description | |
TransactionPropagation | enumeration,
Spring.Transaction.TransactionPropagation | optional propagation setting. Required, Supports, Mandatory, RequiresNew, NotSupported, Never, Nested | |
Isolation |
System.Data.IsolationLevel
| optional isolation level | |
ReadOnly | boolean |
| |
EnterpriseServicesInteropOption | enumeration System.Transactions.EnterpriseServicesInteropOption | Options for interoperability with COM+ transactions (.NET 2.0 and TxScopeTransactionManager only) | |
Timeout | int (in seconds granularity) | the transaction timeout | |
RollbackFor | an array of Type objects | an optional array of exception classes that must cause rollback | |
NoRollbackFor | an array of Type objects | an 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.
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
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>
This is not longer a common way to configure declarative transactions but is discussed in the "Classic Spring" appendiex here.
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
)
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 | |
---|---|
As you will immediately see in the examples that follow, using
the |
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
.
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.
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.
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.
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.