By default, Spring's Transactional annoation won't rollback on checked exceptions.
One can use Transactional's rollbackFor attribute to override this.
Is it possible to override this behaviour globally rather than on each annotation?
I have methods that throw IOException. A possibility could be wrapping those exceptions in RuntimeException, as they are unrecoverable for my code.
There are two approaches that I am aware of:
Create your own Meta-Annotation that handles your particular
transaction semantics
Write an aspect (either around or after-throwing) that wraps all checked exceptions in RuntimeExceptions
Related
I was reading this article on Exception handling in Spring MVC and in
"what to use When " section of that blog, it mentions
For exceptions you write, consider adding #ResponseStatus to them.
For all other exceptions implement an #ExceptionHandler method on a #ControllerAdvice class or use an instance of
SimpleMappingExceptionResolver. You may well have
SimpleMappingExceptionResolver configured for your application
already, in which case it may be easier to add new exception classes
to it than implement a #ControllerAdvice.
Is there any good reason why we should not put all handling in #controlleradvice?
Thanks.
For example if you would write your own exception for NotFound - which is pretty self-explanatory, you don't have to add anything extra to this exception, writing a handler for it is overkill - you can just add a #ResponseStatus to it place some info like (value = HttpStatus.NOT_FOUND, reason = "Not found") which if fixed and be done with it.
Now for exceptions which require logging or require more control over the text - e.g. internationalization you will write it in the handler.
Think they were just trying to avoid the handler overkill for places where it can be handled with one line extra.
I've seen a lot of similar questions but they are usually just tangential to mine.
Here is my code.
#Override //class implements interface
//can also add "rollbackFor = OutOfMemoryError.class" here, to no avail
#Transactional(noRollbackFor = IllegalArgumentException.class)
public ReportTemplate getByCode(final String code)
//throws IllegalArgumentException - see question text
{
if(code == null) {
throw new IllegalArgumentException("ss");
}
}
Consumer (non-transactional) calls this method via autowired interface.
Despite noRollbackFor the transaction (consisting of this method alone) gets rolled back.
Ok I understand people who say "by default it's rollbackFor any unchecked, so since the default is not overridden, that rule still applies". But that should not be true, because that would mean noRollbackFor is useless :) We can try specify rollbackFor as in the comment above, in a hope it will cancel "default" rollbackFor - but this does not help: rollback still happens; looks like this setting is simply added to "default". Worst of all, I can't find a precise specification on how it should work, when noRollbackFor and rollbackFor are covering the same descendant exception (like in my case, RuntimeException vs IllegalArgumentException, who wins?).
Well, I was able to find but one official clarification and it says "the strongest matching rule wins" - but this seems to not be universally corrent, as in my case. Also, for some reason they are overly specific: they say no-rollback-for="InstrumentNotFoundException" means, literally, "any exception other than an InstrumentNotFoundException" would be rolled back (but what about InstrumentNotFoundException's descendants?) And anyway it's greatly outdated: 2.5.x, and newer documentation just says something like "rollbackFor lists exceptions to be rolled back, and noRollbackFor lists exceptions not to be", in a quite contradictory manner, I'd say.
What is even more interesting, the code above stops rolling back if I specify IllegalArgumentException in its "throws" clause. Yes, I don't have to, since its unchecked - but if I do, noRollbackFor finally seems to notice it and behave properly! Does anyone know what manual says this should happen? Or maybe it's a bug?
UPDATE Here are spring transaction logs (in my actual code YargReportTemplateDao$TemplateNotFoundException there is everywhere instead of IllegalArgumentException)
1) with "throws"
[qtp22373939-44] TRACE org.springframework.transaction.interceptor.TransactionInterceptor - Completing transaction for [ru.it.p321.dao.YargReportTemplateDaoImpl.getByCode] after exception: ru.it.p321.dao.YargReportTemplateDaoImpl$1: Yarg template was not found for given code: ITORG_REJECT_NOTIFICATION
[pool-28-thread-1] DEBUG org.springframework.transaction.jta.JtaTransactionManager - Initiating transaction commit
[qtp22373939-44] TRACE org.springframework.transaction.interceptor.RuleBasedTransactionAttribute - Applying rules to determine whether transaction should rollback on ru.it.p321.dao.YargReportTemplateDaoImpl$1: Yarg template was not found for given code: ITORG_REJECT_NOTIFICATION
[qtp22373939-44] TRACE org.springframework.transaction.interceptor.RuleBasedTransactionAttribute - Winning rollback rule is: NoRollbackRuleAttribute with pattern [ru.it.p321.dao.YargReportTemplateDao$TemplateNotFoundException]
2) without "throws
[qtp21176461-48] TRACE org.springframework.transaction.interceptor.TransactionInterceptor - Completing transaction for [ru.it.p321.dao.YargReportTemplateDaoImpl.getByCode] after exception: org.springframework.dao.InvalidDataAccessApiUsageException: Yarg template was not found for given code: ITORG_REJECT_NOTIFICATION; nested exception is ru.it.p321.dao.YargReportTemplateDaoImpl$1: Yarg template was not found for given code: ITORG_REJECT_NOTIFICATION
[qtp21176461-48] TRACE org.springframework.transaction.interceptor.RuleBasedTransactionAttribute - Applying rules to determine whether transaction should rollback on org.springframework.dao.InvalidDataAccessApiUsageException: Yarg template was not found for given code: ITORG_REJECT_NOTIFICATION; nested exception is ru.it.p321.dao.YargReportTemplateDaoImpl$1: Yarg template was not found for given code: ITORG_REJECT_NOTIFICATION
[qtp21176461-48] TRACE org.springframework.transaction.interceptor.RuleBasedTransactionAttribute - Winning rollback rule is: null
[qtp21176461-48] TRACE org.springframework.transaction.interceptor.RuleBasedTransactionAttribute - No relevant rollback rule found: applying default rules
The behavior you see has nothing to do with transaction handling but with exception translation. By default when a class is annotated with #Repository Spring will register a PersistenceExceptionTranslationInterceptor which will translate exceptions to one of the Spring DataAccessExceptions. By default it translates all exceptions UNLESS the exception thrown is declared in the throws clause.
This exception translation happens before the TransactionInterceptor and that way there is never an IllegalArgumentException thrown because that already got translated into something else.
Create your won custom exception (Extending from exception ) and apply noRollBackFor you custom exception class
Currently, I use openSession(), begin and commit transaction in a method with classic dao and service layers and save/update/delete multiple object in it. If any error exist I am sure that any save/update in this method did not done.
Is it possible to save more than one object in a method called with #Transactional annotation via getCurrentSession() and it is safe if this guarantee that all object in this method saved/updated or any did not done? And if possible how can I use rollback in this method?
Yes you can, with proper transactional semantics.
#Transactional has a property named rollbackFor = TypeOfException.class. By default it rolls back RuntimeException, but you can specify your own or just Exception for any checked exception.
You should have a proper propagation aswell which again you can specify as a property on #Transactional. Note that if you set propagation = REQUIRES_NEW and an existing transaction calls this method, that will rollback for the nested transaction only. What you're trying to achieve, the most common one I think is to specify propagation = REQUIRED - this will not run on its own transaction but proceed on the existing one, which means that on case of failure the whole transaction will rollback.
I'm working on a bunch of legacy code written by people before me and I'm confused about a particular kind of setup and wonder if this has ever worked to begin with.
There is a managed bean in spring that has a transactional method.
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Throwable.class)
public boolean updateDraftAndLivePublicationUsingFastDocumentsOfMySite(List<FastDocumentLite> fastDocumentLites, Long mySiteId) throws Exception { ... }
Now inside that method I find new instantiations calling update methods fe:
boolean firstFeed = new MySiteIdUpdate(publishing, siteDao, siteDomainService).update(siteId, fastDocumentLites.get(0).getMySiteId());
From my understanding on IOC this new class isn't managed by spring , it's just a variable in the bean. Now going further inside the update method you see another service gets called.
#Transactional(propagation=Propagation.REQUIRED, rollbackFor=Throwable.class)
public void activateSubdomainForSite(Long siteId, boolean activationOfSite)
So if there is a transaction open it should be propagated into this service. But here is what I don't get if that MySiteIdUpdate object isn't managed by spring does the first transaction move forward to the activateSubdomainForSite method ?? Or is another transaction being opened here. I looked in the logs and I believe it to be the latter but I rather ask the experts for a second oppinion before I proclame this legacy code to be complete rubbish to the project lead. I'm suffering with a StaleStateException somewhere further down the road and I'm hoping this has anything to do with it.
I think the code is correct, and the second #Transactional should reuse the existing transaction.
Because:
1) Spring Transaction handling is done either by Proxies or by AspectJ advices. If it is done by Proxies then it is required that MySiteIdUpdate invoke an instance that is injected (this is what you did). If you use AspectJ, then it should work anyway.
2) The association Transactions to the code that use is done by the Thread, this mean, as long as you "are" in the thread which started the transaction you can use it. (you do not start an new thread, so it should work)
An other way to explain: It is perfect legal when you have some method in your call hierarchy that does not belong to an spring bean. This should not make the transaction handling fail.
If I have a Spring Controller with two SEPARATE methods, one annotated by:
#ExceptionHandler(Exception.class)
and another annotated by:
#ExceptionHandler(SubException.class)
And my controller throws SubException.class, does it get handled by both methods, or just #ExceptionHandler(SubException.class)?
One handler will be invoked on a best fit basis.
The exact implementation is in AnnotationMethodHandlerExceptionResolver.findBestExceptionHandlerMethod(Object,Exception)
You can create your own annotation class which act as exception. And after that you need to provide your class annotation instead of Exception-handler.
Please let me know, if you have any query.
Handlers typically implement Spring’s Ordered interface so you can define the order that the handlers run in.
see Exception Handling in Spring MVC