Compared to its Java peer, Spring.NET’s transaction support is still in its infancy and somewhat undocumented.
According to some posts in the forum, Transaction support should come with version 1.1 (currently in CVS).
In the meantime, those of us who have to deal with transactions still have to find a solution.. The first step is to get a nightly snapshot build of the CVS and copy the Spring.Data classes from the sandbox.
The second step is to understand how Transactions are supported and the last step is to add support for your Data backend if it is not yet supported (only ADO.NET is supported, as far as I know.. Maybe iBatis.Net is too, at least partially).
So, concerning the second step, this thread should help to understand once people give some answers…
This post is to give more details of what I understand in the hope that it will help someone else in the same position :
- First of all, It is necessary to understand the real problem, so here is a scenario :
- A Database maintains all data for a website
- A website has several visitors working concurrently (Several threads will be concurrently accessing the database at the same time, each one serving a visitor)
- It is NOT acceptable (for performance reasons) to open a new connection every time something must be SELECT’ed or UPDATE’d, then closed.
- Database sessions can either be commit’ed (successful registration on the website) or rollback’ed (client gone before the end of the registration).
- Then, it is important to read about Spring (Java) Transaction support, since Spring.NET is highly inspired about its Java counterpart. It’s basically the only documentation we have, so let’s read it, it’s way easier to understand than API documentation
- So, to sum-up Spring Transaction Support: The PlatformTransactionManager (e.g. AdoPlatformTransactionManager for ADO.NET), TransactionSynchronizationManager and Template (AdoTemplate) classes are particularly important.
- All Data access code should be run by PlatformTemplate.Execute (AdoTemplate) that will take care of all the resources initializasation/cleanup, as well as transaction synchronization if necessary (the Template still works if there is no transaction to synchronize to).
- TransactionSynchronizationManager takes care of managing resources and transaction synchronizations per thread. In fact, the problem to solve is about assigning a Transaction and its corresponding connection to every thread that wants to access the database. Depending on the underlying infrastructure and configuration, we may reuse already existing connections -instead of creating and closing systematically – (connection pool such as Commons-DBCP in the Java World) and synchronize them to a transaction. However, this class does not deal with anything else than just keeping the resources of each thread. All the connection/transaction to thread assignment logic is done by the PlatformTransactionManager
- PlatformTransactionManager (AdoPlatformTransactionManager) : this component returns a currently active transaction or creates a new one based on the given parameters (Isolation level, etc).
- TransactionTemplate complements AdoTemplate. While AdoTemplate is used to make sure data access code will be synchronized to a currently used transaction, TransactionTemplate is used to demarcate transactions. In other words, it can be used in non-data access code (higher-level services that use DAOs directly or indirectly), to tell where a transaction begins and where it ends.
- The use of TransactionTemplate is not mandatory, but not using it will imply that calling AdoPlatformTransactionManager methods will be up to the user…
- Declarative TransactionDemarcation can also be used (and is recommended), but for the sake of clarity, we won’t add another layer of complexity here. Spring.NET’s AOP-style declarative transaction demarcation can be seen as the dynamic creation of a proxy around a service class that will just wrap the method calls inside the previously described TransactionTemplate. Then, other beans must use the generated proxy instead of the actual class. But as you can see, the underlying principle concerning Transaction stays the same : we rely on a TransactionTemplate.
So, overall, things do not seem that complex.. Let’s analyze a bit more.
PlatformTransactionManager is the main actor on the scene. Its role is to Create/Reuse transactions, commit and rollback transactions. It conforms to the IPlatformTransactionManager interface that defines the following methods :
- ITransactionStatus GetTransaction( ITransactionDefinition definition );
- void Commit( ITransactionStatus transactionStatus );
- void Rollback( ITransactionStatus transactionStatus );
ITransactionDefinition defines all the gory details of the transaction (Propagation behavior, Isolation Level, Timeout, etc). See Wikipedia for more information about these general Database concepts.
So basically, when we want to perform a transacted set of tasks, the code should look like (taken from Spring Java documentation) :
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
TransactionStatus status = txManager.getTransaction(def);
try {
// execute your business logic here
}
catch (MyException ex) {
txManager.rollback(status);
throw ex;
}
txManager.commit(status);
In other words, this is the manual way of demarcating transactions (e.g. the “wrong” way). But at least, things are pretty clear about what’s done on the system. (txManager is the platform transaction manager, AdoPlatformTransactionManager, for example).
The good news is that it is exactly how TransactionTemplate wraps service code, in the Execute() method :
public object Execute(ITransactionCallback callback)
{
ITransactionStatus status = _platformTransactionManager.GetTransaction( this );
object result = null;
try
{
result = callback.DoInTransaction( status );
}
catch ( Exception ex )
{
rollbackOnException( status, ex );
throw ex;
}
_platformTransactionManager.Commit( status );
return result;
}
As you can see from the code no magic is done here, we purely rely on the PlatformTransactionManager to perform some logic that will make sure the connections and transactions don’t get mixed and matched between threads…
So, let’s see how PlatformTransactionManager works.. Of course, you can already envision that a lot of things are going to be pretty much the same no matter what the concrete platformTransactionmanager is. In fact, no matter whether you use ADO.Net, iBatis.Net, or whatever else, the following workflow handling is hardcoded into AbstractPlatformTransactionSupport, from which all concrete PlatformTransactionsupport should inherit :
- Determines if there is an existing transaction
- Applies the appropriate propagation behavior
- Suspends and resumes transactions if necessary
- Checks the rollback-only flag on commit
- Applies the appropriate modification on rollback (actual rollback or setting rollback-only)
- Triggers registered synchronization callbacks (if transaction synchronization is active)
The basic idea here is that the three methods (GetTransaction(), Commit() and Rollback() are implemented and use the Strategy Design Pattern to delegate actual processing to the concrete subclasses (All the Do*() methods).
So, all the dirty work of complying with the API is already done, and the real work left is about coding the parts that are different with each system, such as beginning a transaction (DoBegin()).
The link with TransactionSynchronizationManager is done, for example in DoBegin by :
TransactionSynchronizationManager.BindResource(DbProvider,
txMgrStateObject.ConnectionHolder);
The ConnectionHolder (which represents the Connection + Transaction) is bound to the thread by the static BindResource which will use a [ThreadStatic] Collection to take care of the Threading stuff. (See more information about the TreadStatic Attribute on MSDN).
However, one thing that I am still unsure about is how connection ressources are disposed. After committing, we do not want to close the session, we just want to tell the PlatformTransactionManager that the resource is now free to reuse for some other thread.
To put it in a nutshell, in order to add support for a new transaction system, here are the required elements :
- a Template class
- A transaction Manager
- Utils class (used by the template and Manager to create/open/close connections)
This should be enough to get a working implementation. I’ll create one for db4o OO database.
Hi Sami,
it’s nice to hear that you are writing a db4o adapter for Spring.NET. Please post back to db4o forums when it’s ready, to let people here about it.
Thanks!
Best,
Carl
I sure will. and contribute the code back to spring.net developers
[...] As previously stated, I am currently working on Spring.NET and db4o Integration. [...]
[...] Adding db4o OO database support for Spring.NET [...]