View Full Version : UnexpectedRollbackException in a rollback JUnit test
nilesh
Jul 20th, 2005, 03:38 PM
I have a JUnit test that uses AbstractTransactionalDataSourceSpringContextTests. I recently upgraded from Spring 1.1.5 to 1.2.2 and the test fails (succeeded in 1.1.5) with the following exception:
org.springframework.transaction.UnexpectedRollback Exception: Transaction has been rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPl atformTransactionManager.commit(AbstractPlatformTr ansactionManager.java:417)
at org.springframework.transaction.interceptor.Transa ctionAspectSupport.doCommitTransactionAfterReturni ng(TransactionAspectSupport.java:258)
at org.springframework.transaction.interceptor.Transa ctionInterceptor.invoke(TransactionInterceptor.jav a:67)
at org.springframework.aop.framework.ReflectiveMethod Invocation.proceed(ReflectiveMethodInvocation.java :144)
at org.springframework.aop.framework.JdkDynamicAopPro xy.invoke(JdkDynamicAopProxy.java:174)
at $Proxy0.loginCheck(Unknown Source)
at com.mycompany.ws.common.LoginCheckInterceptor.befo re(LoginCheckInterceptor.java:25)
at org.springframework.aop.framework.adapter.MethodBe foreAdviceInterceptor.invoke(MethodBeforeAdviceInt erceptor.java:52)
at org.springframework.aop.framework.ReflectiveMethod Invocation.proceed(ReflectiveMethodInvocation.java :144)
at org.springframework.aop.framework.JdkDynamicAopPro xy.invoke(JdkDynamicAopProxy.java:174)
at $Proxy44.getShelfInfoList(Unknown Source)
at component.service.EquippingServiceImplTests.testMi ssingInput(EquippingServiceImplTests.java:91)
It appears to be failing when a MethodBeforeAdvice is executed (LoginCheckInterceptor above which calls a method loginCheck in a class called LoginServiceImpl). loginCheck executes in a transaction, as does the class that is being advised (both using TransactionProxyFactoryBean). In both cases the transaction attributes are PROPAGATION_REQUIRED for all methods.
Strangely, I haven't seen this occur with other tests that do similar things. The only thing unusual about this test is that it has multiple calls to the same advised method and in each case an exception is thrown and caught (it expects an exception).
I just tried putting one of these method calls in its own unit test, and it does not exhibit this problem. It only appears when I have more than one calls.
Juergen Hoeller
Jul 20th, 2005, 04:23 PM
Outside of JTA, that can only happen if an inner transaction has marked the global resource holder as rollback-only - probably as result of its rollback rules -, with the outer transaction not noticing this and still calling commit. To make the caller of the outer transaction aware that what actually happened wasn't a commit, the commit call fails with an UnexpectedRollbackException.
So I would recommend to check out what's going on in detail. Within the scope of the transaction that fails here, is there an inner transaction (probably PROPAGATION_REQUIRED) that might fail and mark the entire transaction as rollback-only? Why doesn't the exception get propagated from there and cause the outer transaction to cause a rollback as well?
Maybe the answer is in the aspects that you're weaving in, which don't necessarily see an exception that's passed up the call stack. Anyway, you need to figure out where the transaction gets marked as rollback-only, and why the outer transaction doesn't notice this and still calls commit - with the result being the UnexpectedRollbackException.
Juergen
Juergen Hoeller
Jul 20th, 2005, 04:25 PM
BTW, this strict UnexpectedRollbackException behavior has been introduced in Spring 1.2.1, to avoid the situation where a commit call results in a rollback without the caller noticing it. This change was made in response to a request from a user who suffered from such unnoticed rollbacks. An example for a bug fix that introduced stronger enforcement of a rule, I guess.
Juergen
adepue
Jul 20th, 2005, 05:04 PM
I would think the newer behavior better as it could help uncover potential bugs, no?
- Andy
Juergen Hoeller
Jul 21st, 2005, 02:26 AM
I agree - that's why the new behavior has been introduced in the first place :-) A commit call should not execute without notice that the actual thing that happened was a rollback.
Juergen
nilesh
Jul 21st, 2005, 08:59 AM
Thanks for the response. I think I understand what is going on now, and my unit test was actually performing behavior that should result in the error.
In case anyone is interested in the details:
My understanding of AbstractTransactionalDataSourceSpringContextTests and its superclass AbstractTransactionalSpringContextTests (which roll back the transaction at the end of each test) is that it starts a transaction and rolls back the transaction programmatically in the onTearDown() method:
(line 165 of AbstractTransactionalSpringContextTests)
this.transactionManager.rollback(this.transactionS tatus);
Since my test is calling methods that throw an exception (and are wrapped in a PROPAGATION_REQUIRED transaction) and this triggers a rollback, the transaction (which includes the outer transaction) gets marked as rollback-only, and even though I catch the exception, when I call another method that is wrapped in a transaction (also PROPAGATION_REQUIRED), it fails on commit with this error since the transaction has been set to rollback.
cyboc
Nov 18th, 2005, 05:58 PM
The only thing unusual about this test is that it has multiple calls to the same advised method and in each case an exception is thrown and caught (it expects an exception).
I just tried putting one of these method calls in its own unit test, and it does not exhibit this problem. It only appears when I have more than one calls.
Hi, I have the exact same problem as nilesh when upgrading from Spring 1.1.5 to 1.2.3. I have a Transactional JUnit test that calls a transactionally advised method that returns an expected exception multiple times. For example:
public class MyTest extends AbstractTransactionalDataSourceSpringContextTests
{
...
public void testSaveClientForVariousExceptions()
{
...
try
{
clientService.saveClient(client); // transactionally advised
fail("should have thrown SomeException");
}
catch (SomeException e)
{
}
...
try
{
clientService.saveClient(client); // second call causes UnexpectedRollbackException
fail("should have thrown SomeOtherException");
}
catch (SomeOtherException e)
{
}
}
...
}
On the second call to the transactionally advised saveClient() method, I get the UnexpectedRollbackException. Like nilesh, my test worked fine with the older version of Spring.
I think I understand Juergen's explanation but I'm wondering how to fix my test? I know one way is to split the test up so that each expected exception is tested in its own test method like so:
public class MyTest extends AbstractTransactionalDataSourceSpringContextTests
{
...
public void testSaveClientForSomeException()
{
...
try
{
clientService.saveClient(client); // transactionally advised
fail("should have thrown SomeException");
}
catch (SomeException e)
{
}
}
public void testSaveClientForSomeOtherException ()
{
...
try
{
clientService.saveClient(client);
fail("should have thrown SomeOtherException");
}
catch (SomeOtherException e)
{
}
}
...
}
However, I'd rather not do that right now because some of my test methods test many for many exceptions. It would be a lot of work to refactor that right now.
So, I wondering is there another way to fix this? For example, could I override something in AbstractTransactionalDataSourceSpringContextTests? Alternatively, is there something I can call in the catch{} to reset the transaction?
Colin Sampaleanu
Nov 19th, 2005, 10:41 PM
First of all, I want to make the point that in the case of transactional methods wrapping other methods which are transactional with a PROPAGATION_REQUIRED propagation setting, which seems to be the case here (the base test class creates a transaction around the test method, but the service method invocations in the test just join in the transaction), it's a bit of a misnomer to talk about an outer and an inner transaction. There is only one transaction, which is being joined by the inner method call.
Anyway, something weird is going on here if the calls from the inner saveClient() method calls are throwing the unexpected rollback exception. I can not think of any circumstance in this case under which this should ever be considered a real point to commit (and therefore cause the error on that commit), as the method invocation has not actually caused any transaction to be created, and the commit should only happen when the outer (test) method itself returns. So the state of the rollback flag in the transaction should be irrelevant. And if Spring behaves like EJB 2.1 (see section 17.6.2.2) that should certainly be the case.
Joe, this is all with the same transaction manager, and with transactions set to PROPAGATION_REQUIRED? What is the transaction manager?
I will ping Juergen here on this, as again, I can't think of a good reason why this should ever happen.
Colin
cyboc
Nov 21st, 2005, 12:33 PM
Colin, thanks for your reply.
There is only one transaction, which is being joined by the inner method call.
Correct. There is only one transaction here, which begins when the test method begins.
Anyway, something weird is going on here if the calls from the inner saveClient() method calls are throwing the unexpected rollback exception. I can not think of any circumstance in this case under which this should ever be considered a real point to commit (and therefore cause the error on that commit), as the method invocation has not actually caused any transaction to be created, and the commit should only happen when the outer (test) method itself returns. So the state of the rollback flag in the transaction should be irrelevant.
I agree. I was confused by this behaviour too.
Joe, this is all with the same transaction manager, and with transactions set to PROPAGATION_REQUIRED? What is the transaction manager?
Yes, all with the same transaction manager. Yes, transactions were set to PROPAGATION_REQUIRED. The transaction manager was org.springframework.orm.hibernate3.HibernateTransa ctionManager. And in case it wasn't clear, saveClient() is set to PROPAGATION_REQUIRED.
I have a similar situation with my tests for deleteClient(). The only difference is that the second call to deleteClient() is NOT expected to throw an exception, whereas the second call to saveClient() is expected to throw an exception. However, the result is the same in both test methods -- the second call to the transactionally advised service method results in a UnexpectedRollbackException, after the first call threw an application exception. Here is the code for testDeleteClient():
public class MyTest extends AbstractTransactionalDataSourceSpringContextTests
{
...
public void testDeleteClient()
{
final long someClientId = 1; // NOT allowed to delete this client
Client client = (Client) clientService.loadClient(someClientId);
try
{
clientService.deleteClient(client); // transactionally advised
fail("should have thrown DeleteNotAllowedException");
}
catch (DeleteNotAllowedException e)
{
}
...
final long someOtherClientId = 2; // this client can be deleted
client = (Client) clientService.loadClient(someOtherClientId);
clientService.deleteClient(client); // second call causes UnexpectedRollbackException
...
}
...
}
I'm not sure whether this is considered to be a bug in the framework or not. I leave that up to the Spring team to decide. If you do consider it a bug, please let me know and I'll happily post a bug in JIRA.
Anyway, I'm still left with some tests that don't pass. Any advice as to how I should proceed? Should I restructure my tests so that each expected exception is tested in a separate test method? Or is there some way I can workaround this by subclassing AbstractTransactionalDataSourceSpringContextTests and overriding some behaviour?
Cheers
Costin Leau
Nov 22nd, 2005, 07:36 AM
Sorry for 'piggy-backing' on this post - I had a similar problem with the same exception when using readOnly transactions: http://forum.springframework.org/showthread.php?t=17730
steve_smith
Nov 23rd, 2005, 10:59 AM
cyboc,
Have you try split up the test method into separate test methods, one for each expected exception?
cyboc
Nov 23rd, 2005, 11:28 AM
Have you try split up the test method into separate test methods, one for each expected exception?
Not yet because that would be a lot of work at this point. I am waiting for someone to (hopefully) suggest an easier solution to this problem.
Also, can anyone confirm whether this should be considered a bug? Colin Sampaleanu seemed to suggest that it may be a bug.
cyboc
Nov 24th, 2005, 02:45 PM
FYI, I gave up waiting for suggestions for a semi-quick fix. So, I bit the bullet and rejigged my TestCase to use a separate test method for each expected exception so as to avoid the UnexpectedRollbackException. But I would still like to know whether this is a bug (as Colin suggested it might be) and whether I should report it to JIRA.
Colin Sampaleanu
Nov 24th, 2005, 02:50 PM
The best thing to do is to add it as a bug in Jira (also pointing to this thread from there).
I can not think how the behaviour could be considered correct. Even if Juergen ultimately disagrees, somebody else searching for the issue in Jira will have an idea as to the rationale for it working the way it does.
Regards,
haryon
Aug 23rd, 2007, 04:24 AM
Hi,
we also have the same problem. Now I read all of this, it reminds me of another situation :
I set transaction on every *Service bean, get* gets read-only, others get propagation required. If a first Service bean calls a second service bean, and the second fails with an exception, that the first call catch and handle, then the transaction is still a rollback, because the exception went through the transaction AOP around the second service. The point is : why the transaction is marked as rollback ? I would expect the transaction de rollback is an exception tries to get out of the transaction. In our case, the second call is inside the transaction, and the exception doesn't get out at all.
Would be interesting to see whether this behaviour still happen with other transaction managers (jta for example).
Until this "buggy" behaviour is fixed (or declared as "feature/intended") I'll cut my tests and continue to warn my developers against throwing exceptions out of their beans.
karldmoore
Aug 23rd, 2007, 06:24 AM
If your second service has PROPAGATION_REQUIRED, doesn't it make sense for it to support the current transaction as the documentation describes?
http://www.springframework.org/docs/api/org/springframework/transaction/TransactionDefinition.html#PROPAGATION_REQUIRED
haryon
Aug 23rd, 2007, 06:45 AM
I don't understand what you mean karldmoore. For me, if I say propagation_require, I say that if there is one transaction i use that transaction and if there is none I create one.
What i mean with "i use that existing transaction" is : i don't perform commit or whatever in that transaction. I behave like there is no aop at all, like if the code was inlined where it is called.
Maybe that's not what propagation_required means ?
What's the difference with propagation_nested ?
Marten Deinum
Aug 23rd, 2007, 06:57 AM
If anywhere in a transaction an exception occurs be it by another method call or something else a transaction is (imho logically) marked as rollback, because integrity can no longer be assured.
If you want the behavior as you descibed you will either have to create a new transaction for that method call or suspend the current transaction. Because the operation you are doing isn't part of the current transaction. (SUPPORS_NEW, SUPPORTS_NESTED).
haryon
Aug 23rd, 2007, 07:56 AM
Hmm, ok, I think I start to see what you mean. But, do you have an example of what could happen that would break integrity ?
Would you also mean that if a single method would write (I agree it's total dumb code) :
try {
throw new ExampleException();
} catch (ExampleException e) {
}
then you should mark the transaction as rollback too ?
Examples of exceptions from one call which would be trapped and handled in a n outer call could be :
- attempt to create an item, if it already exists, then retrieve and update it instead.
- attempt to create an item, if it already exists, delete the existing one, and create the new one.
Would that mean that I should have not declared the subcalls as transactionnal (the first call is coarse-grain service, and is transactionnal, the sub-call is fine-grain and is not transactionnal and should always and only be called from a transactionnal coarse grain i.e. facade) ?
Marten Deinum
Aug 23rd, 2007, 08:08 AM
I'm a firm believer that you shouldn't rely on Exceptions for your normal program flow/execution.
If you want to check if an item already exists, retrieve it and check the result. Don't rely on the exception while first inserting an object, on exception well do an update.
Transactions should obey the ACID (http://en.wikipedia.org/wiki/ACID) principals.
Rollback rules can be defined, however the default is that RuntimeExceptions and Errors result in a rollback, for checked Exceptions you will have to define something. So your example might or might not rollback the Transaction (assuming the Exception is a checked one) depending on the configuration.
Your Transaction definition highly depends on the use case/business logic you are trying to solve.
The largest part of our application uses REQUIRED, however some parts are allowed to fail and shouldn't affect the remainder of the ongoing transaction. We define those as REQUIRES_NEW, which will result in a new transaction being started for that method, any exceptions will be catched and handled and the normal flow will proceed. However this is an exceptional case, because we put something in a database which highly depends on triggers and configurable constraints (is an already existing application) we needed to handle those exceptions and base our actions on it.
dejanp
Aug 23rd, 2007, 08:09 AM
Tha't what user exceptions (non-runtime exceptions) are for. They do not imply rollback unless you explicitly declare it.
On the other hand the possibility to actually handle data access errors in the same transaction are limited by the implementation you use. JDBC implementation would be no problem, but Hibernate can't do it. Once you get a HibernateException the only thing you can do with that session is close it and do the rollback, nothing else is guaranteed to work.
haryon
Aug 23rd, 2007, 08:16 AM
ok. This makes sense.
RuntimeException are designed to pierce through because we don't expect them, and don't know how to handle them. So rollbacking when we see one pass is logical.
CheckedException are for business exceptions.
I think the error in the architecture of the software I work on is that business exceptions are declared RuntimeException.
---
Is that default behaviour that non runtime exceptions do not set rollback ?
Or do I have to configure Spring for it (I have seen some -MyException in some spring files) ?
dejanp
Aug 23rd, 2007, 08:33 AM
Reference manual "9.5.3 Rolling back"
However, please note that the Spring Framework's transaction infrastructure code will, by default, only mark a
transaction for rollback in the case of runtime, unchecked exceptions; that is, when the thrown exception is an
instance or subclass of RuntimeException. (Errors will also - by default - result in a rollback.) Checked
exceptions that are thrown from a transactional method will not result in the transaction being rolled back.
EdwinDhondt
Feb 1st, 2010, 11:06 AM
I'm using Spring 2.5.6, Spring TestContext framework, Hibernate 3.3.2 and Junit4.
Testclass annotated with @TransactionManager(defaultRollback=false) and @Transactional annotations.
TransactionManager = spring's hibernate3transactionmanager.
Junit4 Testmethod annotated with @Test.
Spring declarative txn dcl xml file (see below for snippet).
My testmethod calls a service layer method:
getServiceX().deleteXyz();
This deleteXyz() method throws, as I expect, an instance of BusinessException (which I've declared as rollback-for).
Since this service layer method runs within the same txn as my testmethod I expected a smooth rollback to occur.
However although the txn is marked rollback it seems Spring still wants to commit the txn, resulting in an UnexpectedRollbackException.
Why is this occuring ?
More importantly will this also occur in "production" code deployed to my web container ? I definitely don't want it to appear there.
Any advice welcome.
Thanks,
EDH
<tx:advice id="serviceLayerDefaultTxAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="get*" isolation="READ_COMMITTED" propagation="REQUIRED" read-only="true"/>
<tx:method name="*" isolation="READ_COMMITTED" propagation="REQUIRED"
rollback-for="be.vlaanderen.techstack.basis.exception.BusinessEx ception"
no-rollback-for="org.springframework.orm.ObjectRetrievalFailureExce ption"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceLayerMethods"
expression="execution(* be.vlaanderen..*.service.*.*(..))" />
<aop:advisor advice-ref="serviceLayerDefaultTxAdvice" pointcut-ref="serviceLayerMethods" />
</aop:config>
Marten Deinum
Feb 2nd, 2010, 01:46 AM
How are you testing for this exception? Are you using a try/catch inside the test method, if so don't because that breaks transaction management. You should use the @Test and expect a certain exception, which will also instruct spring to rollback the transaction. (You switched the default to commit, so if the exception doesn't bubble up, it doesn't see if something is wrong and hence commits.
EdwinDhondt
Feb 2nd, 2010, 02:47 AM
I was indeed in my testmethod catching the BusinessException thrown by the servicelayer method, thereby swallowing it.
However, I changed my code to the following and I'm still getting an UnexpectedRollbackException:
@Test (expected = BusinessException.class)
public void mytestMethod() throws BusinessException{
Gebruiker gebruiker = getGebruikerService().getGebruiker(...);
try{
getGebruikerService().changePasswordNOKOldPassword Mismatch(...)
}catch (BusinessException e{
assert some things;
throw e
}
}
...Exposing Hibernate transaction as JDBC transaction [jdbc:oracle:thin:@VM-IDE-01:1521:XE, UserName=X_D, Oracle JDBC driver]
...DEBUG [org.springframework.test.context.transaction.Trans actionalTestExecutionListener] - Retrieved @TransactionConfiguration [@org.springframework.test.context.transaction.Tran sactionConfiguration(defaultRollback=false, transactionManager=transactionManager)] for test class [class a.b.c.d.gebruiker.service.GebruikerServiceTest]
...DEBUG [org.springframework.test.context.transaction.Trans actionalTestExecutionListener] - Retrieved TransactionConfigurationAttributes [[TransactionConfigurationAttributes@cc5002 transactionManagerName = 'transactionManager', defaultRollback = false]] for class [class a.b.c.d.gebruiker.service.GebruikerServiceTest]
...DEBUG [org.springframework.test.context.transaction.Trans actionalTestExecutionListener] - No method-level @Rollback override: using default rollback [false] for test context [[TestContext@1835282 testClass = GebruikerServiceTest, locations = array<String>['classpath*:/spring-backend-test-dev-service-layer.xml'], testInstance = a.b.c.d.gebruiker.service.GebruikerServiceTest@9df 354, testMethod = changePasswordNOKOldPasswordMismatch@GebruikerServ iceTest, testException = [null]]]
...INFO [org.springframework.test.context.transaction.Trans actionalTestExecutionListener] - Began transaction (1): transaction manager [org.springframework.orm.hibernate3.HibernateTransa ctionManager@2573a8]; rollback [false]2010-02-02 09:19:55,532 DEBUG [org.springframework.orm.hibernate3.HibernateTempla te] - Found thread-bound Session for HibernateTemplate
...,532 DEBUG [org.springframework.orm.hibernate3.HibernateTempla te] - Not closing pre-bound Hibernate Session after HibernateTemplate
...,562 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Found thread-bound Session [org.hibernate.impl.SessionImpl@127d15e] for Hibernate transaction
...,562 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Participating in existing transaction
...,562 DEBUG [org.springframework.orm.hibernate3.HibernateTempla te] - Found thread-bound Session for HibernateTemplate
...,582 DEBUG [org.hibernate.jdbc.AbstractBatcher] - about to open PreparedStatement (open PreparedStatements: 0, globally: 0)
...,582 DEBUG [org.hibernate.SQL] -
...
(a Hibernate query that gets executed)
...
...,642 DEBUG [org.hibernate.jdbc.AbstractBatcher] - about to open ResultSet (open ResultSets: 0, globally: 0)
...,642 DEBUG [org.hibernate.loader.Loader] - result row: EntityKey[a.b.c.d.gebruiker.domain.Gebruiker#UT-GBRKR-005]
...,652 DEBUG [org.hibernate.jdbc.AbstractBatcher] - about to close ResultSet (open ResultSets: 1, globally: 1)
...,652 DEBUG [org.hibernate.jdbc.AbstractBatcher] - about to close PreparedStatement (open PreparedStatements: 1, globally: 1)
...,652 DEBUG [org.hibernate.engine.TwoPhaseLoad] - resolving associations for [a.b.c.d.gebruiker.domain.GebruikerImpl#UT-GBRKR-005]
...,672 DEBUG [org.hibernate.engine.TwoPhaseLoad] - done materializing entity [a.b.c.d.gebruiker.domain.GebruikerImpl#UT-GBRKR-005]
...,672 DEBUG [org.hibernate.engine.StatefulPersistenceContext] - initializing non-lazy collections
...,672 DEBUG [org.springframework.orm.hibernate3.HibernateTempla te] - Not closing pre-bound Hibernate Session after HibernateTemplate
...,672 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Found thread-bound Session [org.hibernate.impl.SessionImpl@127d15e] for Hibernate transaction
...,672 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Participating in existing transaction
...,672 DEBUG [org.springframework.orm.hibernate3.HibernateTempla te] - Found thread-bound Session for HibernateTemplate
...,682 DEBUG [org.springframework.orm.hibernate3.HibernateTempla te] - Not closing pre-bound Hibernate Session after HibernateTemplate
...,682 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Participating transaction failed - marking existing transaction as rollback-only
...,682 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Setting Hibernate transaction on Session [org.hibernate.impl.SessionImpl@127d15e] rollback-only...,
702 DEBUG [org.springframework.test.context.junit4.SpringMeth odRoadie] - Test method [public void a.b.c.d.gebruiker.service.GebruikerServiceTest.cha ngePasswordNOKOldPasswordMismatch() throws a.b.techstack.basis.exception.BusinessException] threw exception: a.b.techstack.basis.exception.BusinessException: user_changepwd_pwdold_mismatch
...,702 DEBUG [org.springframework.test.context.transaction.Trans actionalTestExecutionListener] - Retrieved @TransactionConfiguration [@org.springframework.test.context.transaction.Tran sactionConfiguration(defaultRollback=false, transactionManager=transactionManager)] for test class [class a.b.c.d.gebruiker.service.GebruikerServiceTest]
...,702 DEBUG [org.springframework.test.context.transaction.Trans actionalTestExecutionListener] - Retrieved TransactionConfigurationAttributes [[TransactionConfigurationAttributes@17834de transactionManagerName = 'transactionManager', defaultRollback = false]] for class [class a.b.c.d.gebruiker.service.GebruikerServiceTest]
...,702 DEBUG [org.springframework.test.context.transaction.Trans actionalTestExecutionListener] - No method-level @Rollback override: using default rollback [false] for test context [[TestContext@1835282 testClass = GebruikerServiceTest, locations = array<String>['classpath*:/spring-backend-test-dev-service-layer.xml'], testInstance = a.b.c.d.gebruiker.service.GebruikerServiceTest@9df 354, testMethod = changePasswordNOKOldPasswordMismatch@GebruikerServ iceTest, testException = a.b.techstack.basis.exception.BusinessException: user_changepwd_pwdold_mismatch]]
...,702 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Global transaction is marked as rollback-only but transactional code requested commit
...,702 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Initiating transaction rollback
...,702 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Rolling back Hibernate transaction on Session [org.hibernate.impl.SessionImpl@127d15e]
...,702 DEBUG [org.hibernate.transaction.JDBCTransaction] - rollback
...,732 DEBUG [org.hibernate.transaction.JDBCTransaction] - re-enabling autocommit
...,732 DEBUG [org.hibernate.transaction.JDBCTransaction] - rolled back JDBC Connection
...,732 DEBUG [org.hibernate.jdbc.ConnectionManager] - transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!
...,732 DEBUG [org.springframework.jdbc.datasource.DataSourceUtil s] - Resetting isolation level of JDBC Connection [jdbc:oracle:thin:@VM-IDE-01:1521:XE, UserName=LED_D, Oracle JDBC driver] to 2
...,742 DEBUG [org.springframework.orm.hibernate3.HibernateTransa ctionManager] - Closing Hibernate Session [org.hibernate.impl.SessionImpl@127d15e] after transaction
...,742 DEBUG [org.springframework.orm.hibernate3.SessionFactoryU tils] - Closing Hibernate Session
...,742 DEBUG [org.hibernate.jdbc.ConnectionManager] - releasing JDBC connection [ (open PreparedStatements: 0, globally: 0) (open ResultSets: 0, globally: 0)]
...,742 DEBUG [org.hibernate.jdbc.ConnectionManager] - transaction completed on session with on_close connection release mode; be sure to close the session to release JDBC resources!
...,742 WARN [org.springframework.test.context.TestContextManage r] - Caught exception while allowing TestExecutionListener [org.springframework.test.context.transaction.Trans actionalTestExecutionListener@1627b8b] to process 'after' execution for test: method [public void a.b.c.d.gebruiker.service.GebruikerServiceTest.cha ngePasswordNOKOldPasswordMismatch() throws a.b.techstack.basis.exception.BusinessException], instance [a.b.c.d.gebruiker.service.GebruikerServiceTest@9df 354], exception [a.b.techstack.basis.exception.BusinessException: user_changepwd_pwdold_mismatch]
org.springframework.transaction.UnexpectedRollback Exception: Transaction rolled back because it has been marked as rollback-only
at org.springframework.transaction.support.AbstractPl atformTransactionManager.commit(AbstractPlatformTr ansactionManager.java:695)
at org.springframework.test.context.transaction.Trans actionalTestExecutionListener$TransactionContext.e ndTransaction(TransactionalTestExecutionListener.j ava:504)
at org.springframework.test.context.transaction.Trans actionalTestExecutionListener.endTransaction(Trans actionalTestExecutionListener.java:277)
at org.springframework.test.context.transaction.Trans actionalTestExecutionListener.afterTestMethod(Tran sactionalTestExecutionListener.java:170)
at org.springframework.test.context.TestContextManage r.afterTestMethod(TestContextManager.java:344)
I don't understand why Spring's testcontext framework still wants to commit the txn although it has been marked for rollback.
What am I doing wrong ?
Any advice ?
Thanks,
EDH
Marten Deinum
Feb 2nd, 2010, 02:54 AM
Hmm... I think it is the due to the way JUnit handles the expected/after stuff, this is probably also in a try/catch and not propagating the exceptions, hence the tx manager doesn't see the exception, tries to commit but sees that it already has been committed (hence the Unexpected part).
EdwinDhondt
Feb 2nd, 2010, 03:07 AM
Could this be considered a bug ?
Is there a way around ?
Do you agree that my "production" code will be have differently ?
For example.
Let's say you have two services each having a method, Service1.method1 and Service2.method2.
These methods both have transaction "REQUIRED" (defined in Spring xml file).
Let's say object X (non-transactional) calls Service1.method1, and Service1.method1 (marked "REQUIRED"), calls Service2.method2 (also marked "REQUIRED").
I would then assume a txn, txn1 is started upon calling Service1.method1.
I would also assume that Service2.method2 runs in that same txn, txn1, as Service1.method1.
Am I right so far ?
Then, if within Service2.method2 an exception (runtime or not) is raised that is declared as rollback-for and if that exception is not caught and swallowed by Service1.method1, then I would hope that txn1 is rolledback without throwing an UnexpectedRollbackException and therefore that Spring doesn't try to commit this txn (because it has been set for rollback).
Am I still right ?
Thanks,
EDH
Marten Deinum
Feb 2nd, 2010, 03:52 AM
The production code will behave as described, it just fails in the testcase due to the nature of HOW JUnit (not spring) handles exceptions thrown from a test method.
As long as you don't mess around with catching the exception (and swallow it) the behavior is as you describe. The exception is crucial for the transaction management to work.
You might want to raise a JIRA however I don't consider this a spring bug but a JUnit issue, however the spring guys might be able to fix it :).
EdwinDhondt
Feb 2nd, 2010, 06:44 AM
Martin,
I still do have some concerns. Not related to Junit-Spring but to Spring-Hibernate txn handling I guess.
Say two methods that should run in a different txn.
For example:
public class Service1Test{
Service1 service1;
@Test
@NotTransactional
public void testMethod1(){
getService1().method1(xyz);
}
}
public class Service1{
XyzDao xyzDao;
Service2 service2;
public void method1(Xyz xyz){
getXyzDao().create(xyz);
//Do some other stuff like instantiating an Abc abc = new Abc()
getService2().method2(abc);
}
}
public class Service2{
AbcDao abcDao;
public void method2(Abc abc){
getAbcDao().create(abc);
}
}
Service1.method1 = txn "REQUIRED".
Service2.method2 = txn "REQUIRES_NEW".
Service2.method2 throws a DataIntegrityViolationException because I'm trying to save something with a missing not-null column.
I'm calling Service1.method1 from within my non-transactional Junit @Test method.
In the debug output I can see a new txn and a new Session being created upon calling getService1().method1(abc).
I can also see that this first txn is getting suspended and another txn and session being created when calling getService2().method2(abc) from within Service1.method1(). That's fine.
I can see in the debug output that my second txn fails and is marked for rollback, which is as expected because a problem occurred while persisting abc and therefore a DataIntegrityViolation was thrown.
I can see in the debug output that my first txn, which was suspended, is resumed.
But then what I really don't understand is why this first resumed txn is also considered failed and marked for rollback even after adding a catch clause in Service1.method1 ?
Is this logical behaviour ?
I would have expected the first txn not to be marked for rollback, only the second, independent txn. Why else marking Service2.method2 as "REQUIRES_NEW" ?
What's going on here ?
Thanks,
EDH
Marten Deinum
Feb 2nd, 2010, 07:03 AM
If you put a try/catch around the method that should execute in a new transaction and if the exception doesn't bubble up it should work (it is a setup I've used many times, because outcome of tx2 influence the decision in tx1).
EdwinDhondt
Feb 2nd, 2010, 11:44 AM
Marten,
Even with the try/catch I can't get it to work. The first txn is also always rolled back.
Did it work for you from "within" Spring's annotation-driven TestContext framework ?
Did it work for you when executing the code in a web container ?
What combination of Spring and Hibernate are you using ?
Are you using Spring's declarative txn management ?
Thanks,
EDH.
EdwinDhondt
Feb 2nd, 2010, 12:20 PM
I found the problem.
There was something wrong in my declarative txn demarcation rules. :-(
Powered by vBulletin® Version 4.2.1 Copyright © 2013 vBulletin Solutions, Inc. All rights reserved.