Spring 2.5, Hibernate.
Our application is architected to have a services layer and a manager layer. The services layer provides all read only services and communicates to the UI the results. For services requiring changes to the database, the services layer performs all initial read-only functions, then calls the manager layer.
We have set up a transaction advice around our manager layer to catch all exceptions coming out of the manager layer and rollback those transactions. This works pretty well for the most part.
The problem is in situations where we have one manager call another manager to perform some functionality. This functionality throws an exception, which is a real exception when called from the UI, but is handled internally in the client manager and a successful result is returned. The transaction manager sees that an exception was thrown and rolls back the transaction anyway, even though the exception was handled successfully.
Here is the transaction attribute that's causing us grief.
<tx:method name="*" propagation="REQUIRED" rollback-for="Throwable" no-rollback-for="OverridableException"/>
Is there a way that I can specifiy depth on this? I would like to say, only rollback for exceptions in the highest manager layer call and not for the exceptions in subsequent calls on the same transaction.
Thanks!
Typically you want to wrap the calls to the service layer in transactional advice, so that if your one service class makes several calls to different managers, all of that is wrapped in a single transaction - if you are making changes, all of them succeed or rollback together. This is usually known as (among other names) a Unit of Work pattern.
So my advice would be to reconsider where you apply your transaction definitions. But if this is not doable, you might want to change the transaction advice to propagation=REQUIRES_NEW, so that each manager call starts a new transaction - and thus rolling back an inner transaction when one manager class calls another does not rollback the entire transaction.
I ended up refactoring the inside service method to pull out the piece that was throwing the exception into another method. I then call that method to pre-approve what I'm doing from my outside service and call the new method from the inside service to throw the exception. In this way, I've circumvented the exception being thrown.
Wish there was a better way without committing more often.:(
Related
I was trying to understand how the Spring Listener Container handles the transactions within a retry context.
I configured something like this:
<rabbit:listener-container connection-factory="connectionFactory"
transaction-manager="chainedTransactionManager"
channel-transacted="true"
advice-chain="retryAdvice">
<rabbit:listener ref="myMessageProcessor" queue-names="test.messages" method="handleMessage"/>
</rabbit:listener-container>
And I was hoping that the transaction would be contained within the retry, such that if my transaction fails for any reason, I can decide to retry for specific exceptions and for others just send the message to my DLQ.
However, I was surprised to notice that the retry code is contained within the transaction code and not the other way around, which seemed more sensible.
In other words, Spring listener seems to do:
doIntransaction -> doWithRetry -> invokeMyCode
I was hoping it would be like this:
doWithRetry - doIntransaction -> invokeMyCode
My plan was to use a ChainedTransactionManager containing both a JpaTransactionManager and a RabbitTransactionManager here to handle both, the acknowledgement of the messages I read, and the commit of the messages I sent during this transaction and retry my entire transaction depending on certain conditions, but this does not seem to work that way.
Not only that, but after an exception occurs within a transaction, the context might become useless. I need a new transaction for the retry to make sense.
And there is the problem that any exceptions happening during the commit/rollback phase won't be retried, because they occur outside the retry context. I assume they're only retried depending on the ErrorHandler configuration, and not based on my advised code. Unforunately the ErrorHandler does not have a back off policy or the useful RetryContext details counting the number of times I have retried a transaction.
What would be the right or most recommended way to configure a listener witha transaction manager and retry functionality like in this case?
I haven't tried it, but you should be able to achieve your desired behavior by removing the transaction manager from the container and adding a normal Spring TransactionInterceptor to the advice chain (after the retry advice).
When the container has the transaction manager, you are telling it to start the transaction before invoking the listener (which is wrapped in the advice chain).
However, you might get some noise in the log because the container will probably still try to ack/commit the delivery because it "thinks" it's using local transactions (where the interceptor would have already done it, if it has the RabbitTransactionManager configured).
As long as you don't include the RabbitTransactionManager in the chainedTransactionManager this won't happen though; the container will simply use a local transaction.
If you include the RTM, you might need to use manual acks or add a dummy transaction manager to the container to prevent that.
Let us know how you make out; I can take a look tomorrow.
EDIT
As discussed below, using stateful retry is a simpler solution since the message is rejected and redelivered. But, you need a messageId header (or a custom key generator).
In Spring, if I have:
ServiceA.serviceA() -> ServiceB.serviceB() -> ServiceC.serviceC() ->ServiceD.serviceD()
where ServiceD.serviceD() can throw a runtime exception: MyRuntimeException, which is propagated back up to ServiceA.serviceA catch block. Does it matter on which service I put #Transactional(noRollbackFor=[MyRuntimeException.class]) on?
Is there any difference between putting it on any of the services?
Note: All my Services are marked as #Transactional
As you did not give precision on that, I assume that you are using default propagation of PROPAGATION_REQUIRED. In that context, the 4 services will use the same transaction, and if any of the three inner marks the transaction as read-only as a result of the exception, the outer will get a UnexpectedRollbackException to inform it that the asked commit actually resulted in a rollback. From Spring Reference Manual : However, in the case where an inner transaction scope sets the rollback-only marker, the outer transaction has not decided on the rollback itself, and so the rollback (silently triggered by the inner transaction scope) is unexpected. A corresponding UnexpectedRollbackException is thrown at that point. This is expected behavior so that the caller of a transaction can never be misled to assume that a commit was performed when it really was not. So if an inner transaction (of which the outer caller is not aware) silently marks a transaction as rollback-only, the outer caller still calls commit. The outer caller needs to receive an UnexpectedRollbackException to indicate clearly that a rollback was performed instead.. And if the outer transaction decides to rollback the transaction because of the exception, the transaction will obviously be rolled back.
So if none of the services catches the exception, and if you use a propagation of PROPAGATION_REQUIRED, at least the four involved methods have to be annotated with #Transactional(noRollbackFor=[MyRuntimeException.class]).
An alternative of using noRollbackFor=[MyRuntimeException.class] would be to catch the exception in the appropriate method of ServiceD. In that case, the exception will never climb up the stack, and none of the transactional proxies will ever knows it occurred. The commit would then normally happen at the end of the transaction.
Edit per comment :
If you want further control on exception management, you could try to duplicate method : a transactional method, that calls a non transactional one in your service class. And you could call the non-transactional one if you do not want another transactional proxy in the chain. But this has sense only if this use case (a service class calling another service class with special exception requirement) is exceptional.
As an alternative, you could inject the implementations on the other service classes instead of injecting the transactional proxies (#Autowired private ServiceBImpl serviceB;). As you are already have a transaction obtained at the outer level, all DAO operations should be fine, and as there is only one transactional proxy at the outer level, you have one single point of control for exception management. It is rather uncommon to inject classes instead of interfaces, and you should document the why in a red flashing font :-) , but it should meet your requirements.
If you use a combination of
#Transactional(propagation = REQUIRES_NEW, noRollbackFor = MyRuntimeException.class)
then this service can commit independently from the rollback of the parent transaction (which will occur if it is not annotated with noRollbackFor). Otherwise you would have to mark all the transactional methods involved with the same noRollbackFor annotation.
I have a method that has the propagation = Propagation.REQUIRES_NEW transactional property:
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void createUser(final UserBean userBean) {
//Some logic here that requires modification in DB
}
This method can be called multiple times simultaneously, and for every transaction if an error occurs than it's rolled back (independently from the other transactions).
The problem is that this might force Spring to create multiple transactions, even if another one is available, and may cause some performance problems.
Java doc of propagation = Propagation.REQUIRED says: Support a current transaction, create a new one if none exists.
This seems to solve the performance problem, doesn't it?
What about the rollback issue ? What if a new method call rolls back while using an existing transaction ? won't that rollback the whole transaction even the previous calls ?
[EDIT]
I guess my question wasn't clear enough:
We have hundreds of clients connected to our server.
For each client we naturally need to send a feedback about the transaction (OK or exception -> rollback).
My question is: if I use REQUIRED, does it mean only one transaction is used, and if the 100th client encounters a problem the 1st client's transaction will rollback as well ?
Using REQUIRES_NEW is only relevant when the method is invoked from a transactional context; when the method is invoked from a non-transactional context, it will behave exactly as REQUIRED - it will create a new transaction.
That does not mean that there will only be one single transaction for all your clients - each client will start from a non-transactional context, and as soon as the the request processing will hit a #Transactional, it will create a new transaction.
So, with that in mind, if using REQUIRES_NEW makes sense for the semantics of that operation - than I wouldn't worry about performance - this would textbook premature optimization - I would rather stress correctness and data integrity and worry about performance once performance metrics have been collected, and not before.
On rollback - using REQUIRES_NEW will force the start of a new transaction, and so an exception will rollback that transaction. If there is also another transaction that was executing as well - that will or will not be rolled back depending on if the exception bubbles up the stack or is caught - your choice, based on the specifics of the operations.
Also, for a more in-depth discussion on transactional strategies and rollback, I would recommend: «Transaction strategies: Understanding transaction pitfalls», Mark Richards.
If you really need to do it in separate transaction you need to use REQUIRES_NEW and live with the performance overhead. Watch out for dead locks.
I'd rather do it the other way:
Validate data on Java side.
Run everyting in one transaction.
If anything goes wrong on DB side -> it's a major error of DB or validation design. Rollback everything and throw critical top level error.
Write good unit tests.
I'm using the H2 database embedded within a Spring-MVC app.
I have declare Transactions at my service level. In particular I have a case where we do the folllowing:
Start a transaction
Make a webservices call
Make the subsequent database calls
Upon rollback (an exception), the webservices call is manually rolled back, and the Spring Transaction Manager handles calling a rollback on the DB transaction.
But I realized that the existence of the webservices call within the DB transaction is causing a table-wide lock, which causes other users to receive errors (I can produce this on a 1-user system with 2 browsers).
I am trying to plot my best course of action.
Should I be changing the transaction isolation level in Spring, will that affect the DB locking?
Is it just bad form to have a webservice and DB call in the same transaction?
Should I enable row level locking within the H2 DB because of this?
Am I not thinking of other solutions?
I should add that my service method serviceA() calls two other methods webServiceX() and daoMethodY(). serviceA() is encompassed in a transaction because any exception needs to rollback both the webServiceX() (a function I provide), and rollback the daoMethodY() database operations (a function of the DataSourceTransactionManager).
I think your approach is reasonable, and you should definitely try row-level locking if it's possible.
You might want to reconsider your design. Is the database duplicating state which really comes from the web service? In that case, you might want to consider caching the web service calls instead. But that depends on your application.
Alternatively, you might just have to roll your own transaction management. As long as it's generic, it shouldn't be too much trouble. We've done this in our project where we're not using Spring's transactions. Something like
performTransaction() {
doWSCall();
// no need to worry about WS call exception, because DB call won't happen
try {
doDbCall()
} catch (Exception ex) {
rollbackWSCall()
// rethrow ex
}
}
where all the methods are abstract.
I wouldn't have a web service call mingled into a database call. Your method is breaking the rule that says "do one thing well".
Have your service call other web services and the DAO.
Because the TransactionManager has no way to register a XAResource manager, so that it can enlist XAResources in future Transactions.
The only way to solve this problem is to wrap the handle of the interested service interface.
Each method on the interface does the following:
Check if a tx is present
Figure out if the handle for this tx
If not already participating enlist its XAResource using Transaction.enlist(XAResource)
Registers a callback to enable cleanup using Transaction.registerSynchronization(Synchronization).
Does this seem like a reasonable strategy ?
It's a reasonable first draft, but there are some subtleties to watch out for. Getting the corner cases right yourself is hard - you may be better off writing a resource adaptor for your code and having a JCA handle the transaction plumbing, which is what most database and message queue drivers do.
Just because a tx context is present, that does not mean you can enlist with the tx. In particular the spec requires enlistResource to throw RollbackException if the tx is marked rollback only.
Just because the context is present and the tx has a valid state at the time you check it, that does not mean it remains present or valid throughout the lifetime of the method call. You can get race conditions in which the TM calls rollback on the resource whilst your business logic is still running.
The relationship between XAResource object instances, instances of the service interface and transactions is not particularly elegant due to XA's C heritage. In connection oriented APIs like JDBC it is normally one XAResource per Connection, with that XAResource instance potentially managing multiple tx contexts. For connectionless APIs you can use other patterns which may be simpler.
The Synchronization is not necessary. Nor is it necessarily desirable as Synchronizations are volatile so your cleanup won't get called in crash recovery situations. Better to clean up on commit/rollback in non-heuristic cases.