Friday, 7 May 2010

Exotic error message of the week (Spring transactions) - "Application exception overridden by commit exception"

The @Transactional annotation in Spring makes it easy to make things
uh, transactional, right? So why did I get this stackTrace when I
threw an expected exception?

The web service I was testing returned this response:

<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header/>
<env:Body>
<env:Fault xmlns:fault="http://schemas.xmlsoap.org/soap/envelope/">
<faultcode>fault:CO10001</faultcode>
<faultstring>An unknown error occurred</faultstring>
<faultactor>AdminService</faultactor>
<detail>
<java:string xmlns:java="java.io">Transaction rolled back
because it has been marked as rollback-only</java:string>
</detail>
</env:Fault>
</env:Body>
</env:Envelope>


And the log file contained this:


ERROR [Fri May 7 18:29:46 EST 2010:] [[ACTIVE] ExecuteThread: '0' for
queue: 'weblogic.kernel.Default (self-tuning)'] [string] [367]
org.springframework.transaction.interceptor.TransactionInterceptor :
Application exception overridden by commit exception
mycompany.exception.Exception: Bulletin cannot be created because
of active bulletins that were created between 28/04/2010 and
06/05/2010
at mycompany.domain.service.spring.SpringAdminDomainService.checkForExistingBulletins(SpringAdminDomainService.java:441)
at mycompany.domain.service.spring.SpringAdminDomainService.createBulletin(SpringAdminDomainService.java:384)
at mycompany.domain.service.spring.SpringAdminDomainService.manageBulletin(SpringAdminDomainService.java:87)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy391.manageBulletin(Unknown Source)
at mycompany.application.facade.spring.SpringAdminFacade.manageBulletin(SpringAdminFacade.java:183)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:307)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:182)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:149)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:106)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:171)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:204)
at $Proxy392.manageBulletin(Unknown Source)
at mycompany.package.service.ws.jws.JwsAdminService.manageBulletin(JwsAdminService.java:120)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at weblogic.wsee.component.pojo.JavaClassComponent.invoke(JavaClassComponent.java:99)
at weblogic.wsee.ws.dispatch.server.ComponentHandler.handleRequest(ComponentHandler.java:64)
at weblogic.wsee.handler.HandlerIterator.handleRequest(HandlerIterator.java:137)
at weblogic.wsee.ws.dispatch.server.ServerDispatcher.dispatch(ServerDispatcher.java:109)
at weblogic.wsee.ws.WsSkel.invoke(WsSkel.java:80)
at weblogic.wsee.server.servlet.SoapProcessor.handlePost(SoapProcessor.java:66)
at weblogic.wsee.server.servlet.SoapProcessor.process(SoapProcessor.java:44)
at weblogic.wsee.server.servlet.BaseWSServlet$AuthorizedInvoke.run(BaseWSServlet.java:257)
at weblogic.wsee.server.servlet.BaseWSServlet.service(BaseWSServlet.java:156)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:820)
at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:226)
at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:124)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:283)
at weblogic.servlet.internal.TailFilter.doFilter(TailFilter.java:26)
at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:42)
at com.jamonapi.JAMonFilter.doFilter(JAMonFilter.java:57)
at weblogic.servlet.internal.FilterChainImpl.doFilter(FilterChainImpl.java:42)
at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3393)
at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
at weblogic.security.service.SecurityManager.runAs(Unknown Source)
at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:2140)
at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:2046)
at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1366)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:200)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:172)
DEBUG [Fri May 7 18:29:46 EST 2010:] [[ACTIVE] ExecuteThread: '0' for
queue: 'weblogic.kernel.Default (self-tuning)'] [string] [146]
mycompany.package.service.ws.jws.JwsAdminService : Raising
SOAPFaultException:
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}CO10001
faultString: An unknown error occurred
faultActor: AdminService
exception: org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
ERROR [Fri May 7 18:29:46 EST 2010:] [[ACTIVE] ExecuteThread: '0' for
queue: 'weblogic.kernel.Default (self-tuning)'] [string] [152]
mycompany.package.service.ws.jws.JwsAdminService : unexpected
exception
org.springframework.transaction.UnexpectedRollbackException:
Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:695)


Googling on "Application exception overridden by commit exception"
told us that it happens when an exception is thrown but the class is
not marked to rollback on that exception, so it continues and tries to
do a commit. Which isn't the case in my class since I had
rollbackOn=Throwable.class -- so rolling back on ALL exceptions,
including custom checked exceptions.


But then I looked into my classes and my hierarchy is like
Facade ---> Service class
Now what happens is that BOTH are annotated with @Transactional
I think if this is the cause, but I've removed the ones in the
lower-layer class (Service) and left it only in Facade class
(after testing)


That did the trick!!


The lesson for the day:


Multiple levels of @Transactional is not good. Set your transaction
boundaries in the uppermost layer where it's relevant, and that's it.


The service now returns:
<env:Envelope xmlns:env="http://schemas.xmlsoap.org/soap/envelope/">
<env:Header/>
<env:Body>
<env:Fault xmlns:fault="http://schemas.xmlsoap.org/soap/envelope/">
<faultcode>fault:OPC10608</faultcode>
<faultstring>Bulletins exist that must be recalled</faultstring>
<faultactor>AdminService</faultactor>
<detail>
<java:string xmlns:java="java.io">Bulletin cannot be
created because of active bulletins that were created between
28/04/2010 and 06/05/2010</java:string>
</detail>
</env:Fault>
</env:Body>
</env:Envelope>


Which is what I want.


Hope this helps someone out there!

2 comments:

Krishanu Dasmondal said...

I just became fan of you...after a lot of googling i solve my issue ...

Thanks a lot!!
Krish

Shubham Chaurasia said...

Your argument is excellent
But what if we have @Transactional at service layer and we want to propogate the custom Exception till controller?

Offcourse I would not like to put @Transactional at controller.