views:

796

answers:

3

Hi, imagine a transactional, multithreaded java application using spring, jdbc and aop with n classes in m packages all taking part in database transations. Now let's say there is the need to scope an arbitrary set of classes within one transaction. Furthermore there is always one class T within the scope that commits the transaction when called.

Let me give an example for clarity: Given the packages A,B,Z and classes A.Foo, B.Bar and Z.T. The following instances of the respective classes are called (possibly by different callers with other classes in between): A.Foo,B.Bar,A.Foo,Z.T The transactions will be committed only after Z.T is called. Should the application shut down for whatever reason the transaction will never be committed unless Z.T gets involved.

Instances can call each other and, as already mentioned, there is no common entry point calling all instances from a single point of entry (like a service layer) which would make an easy target for spring's transactional tag.

Now the question: can this problem be solved using aspects ? If so, what could be the basic approach ? Thanks.

+1  A: 

Spring's idiom would recommend having a service interface that knows about units of work and a persistence interface that deals with relational databases. The methods in the service interface should map closely to your use cases. The service implementation knows about all the model and persistence packages and classes it needs to accomplish the goals of the use case.

"Instances can call each other and, as already mentioned, there is no common entry point calling all instances from a single point of entry (like a service layer) which would make an easy target for spring's transactional tag."

This sentence tells me that you're doing things in a manner that doesn't lend itself so easily to Spring's idiom. It's hard to tell exactly what you want, but it sounds like you're tossing aside two of the most important layers that Spring recommends. If it seems difficult to go against the grain, perhaps it's your design that needs reworking.

"...different callers with other classes in between..." - maybe you need to declare transactions individually on these callers.

You can declare transactions in XML config using aspects, either with Spring AOP or AspectJ. Spring 2.5 and higher now give you the option of using annotations if you prefer them to XML configuration.

Your description is terribly confusing to me. Maybe that's part of the reason you're having difficulty with it as well. I'd rethink or clarify.

duffymo
I'm dealing with a legacy inhouse framework, so refactoring to Spring's idioms is difficult at best. The given description is generic, however I don't see the confusion. Three different instances a,b,z of three different classes spread across three different packages can be called from anywhere within the application and together form a single transaction. The transaction only gets committed if the third class of the three gets called. So, a gets called, b gets called and c gets called -> commit. B gets called, a gets called, no c gets called, no commit. A gets called, c gets called, commit.
"Legacy"? Spring's not that old. Sounds like the people who started this didn't know Spring. I'd have a hard time believing that refactoring with Spring would be that difficult. Some object has to create those instances that are interacting. The owner of the objects should own the transaction as well. "Anywhere inside the application" - sounds like a mess. Sorry, can't help you.
duffymo
I can say this: the framework does what it is supposed to do and in this respect it is well designed. Changing it towards springs accepted idioms would be possible but too much work.
A: 

With spring transactions and aop you can do it but it'll be a bit of "hack"...

You'll need to put the start of the transaction at all the entry points - you can only commit from when you started the transaction, and you'll need a second aspect inside this one to control whether to commit or not.

Now the only way to tell spring to roll back a transaction is to throw an exception across the transaction boundary. Thus what you'll need to do if you enter that area Z which will cause the commit you'll then need to put something in the thread local (also possibly via an aspect) which that "inner" aspect will find and thus not throw the exception to roll back the transaction. If you do not enter Z then the thread local will not get the flag and when you go back across the inner aspect an exception will get thrown to roll back the transaction. You'll probably have to swallow that exception.

Michael Wiles
+2  A: 

You don't need a single point of entry, but you do need the ability to apply the transactional interceptor to all entry points so that re-entrant calls can participate in the same transaction. Assuming that you can do that, you could accomplish this with a ThreadLocal flag and a custom org.springframework.transaction.support.TransactionSynchronization implementation.

You'd modify Z.T to set the ThreadLocal flag when a commit is safe to proceed. In your TransactionSynchronization.beforeCommit() implementation, which is invoked from the PlatformTransactionManager, you can check the flag and use that to determine whether to allow the commit to proceed. You can force a rollback by throwing a RuntimeException if the flag is not present.

One caveat would be if you have other types of transactions (that don't involve the 3 co-ordinating classes you've described), you'll need to ensure that they don't get rolled back inadvertently. To do this, you could flag this "special transaction" in A.Foo, B.Bar and Z.T via another ThreadLocal flag, then check that flag in a guard clause in the beforeCommit() method mentioned above. Pseudocode:


void beforeCommit() {
  if in special transaction
    if commit flag not set
       throw new RuntimeException("cancel transaction")
    end if
  end if
end

And, obviously, this is a hack and I wouldn't advocate doing in a greenfield system :).

pbourke
That's a beginning point I can work from. Thanks for the helpful answer.