0

I have a small Spring app that reads a few databases, and writes a small table in one database. I have a @Service-annotated class with a @Transactional method. That method calls a method in a DAO class (which is not @Repository-annotated) which first deletes some rows from a table and then inserts rows to the same table.

This gets deployed to WebLogic. Under normal operation this app is working perfectly fine.

I tried an experiment of deliberately mucking the SQL for the "insert", and deployed this to my local box, and then executed the JMX operation that executes this service operation. After it failed (expected) I checked the database, and I confirmed that the table was intact, so it correctly rolled back the "delete" when the "insert" failed.

My problem is that my integration test that tries to simulate a similar scenario is NOT behaving transactionally. I mocked the JdbcTemplate so it performed the delete, but forced it to throw a DataAccessException on the insert. Afterwards, I checked the database, and the rows were gone, so it didn't rollback the delete as I hoped.

I turned on debug in the Spring JTA package, and I saw the debug message print that says it was rolling back the transaction.

I'm using the Atomikos transaction manager for my tests. The following is an excerpt from the context I'm using in test to define the "catalogTransactionManager", which is what is referenced in the rest of the context.

<!-- Construct Atomikos UserTransactionManager, needed to configure Spring -->
<bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager"
      init-method="init" destroy-method="close">
    <!-- when close is called, should we force transactions to terminate or not? -->
    <property name="forceShutdown">
        <value>true</value>
    </property>
</bean>

<!-- Also use Atomikos UserTransactionImp, needed to configure Spring -->
<bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp">
    <property name="transactionTimeout">
        <value>300</value>
    </property>
</bean>

<!-- Configure the Spring framework to use JTA transactions from Atomikos -->
<bean id="catalogTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
    <property name="transactionManager">
        <ref bean="atomikosTransactionManager" />
    </property>
    <property name="userTransaction">
        <ref bean="atomikosUserTransaction" />
    </property>
</bean>

It probably doesn't matter, but here's my test method (with some things obfuscated):

@Test
public void testInsertFailsAfterDelete() {
List<ErrorMessageInfo>  commonErrorMessagesBefore   =
    myService.
    getMyDAO().getCommonErrorMessages(MyService.CHANNEL_NAME);

JdbcTemplate  template    = mock(JdbcTemplate.class);
myService.getMyDAO().setJdbcTemplate(template);

when(template.update(eq(MyDAO.SQL_DELETE_CHANNEL), any())).
thenReturn(getOrigTemplate().update(MyDAO.SQL_DELETE_CHANNEL, MyService.CHANNEL_NAME));

DataAccessException    exception   = new DataAccessException("insert failed") {};

when(template.update(eq(MyDAO.SQL_INSERT_ERROR_TO_CHANNEL), anyString(), anyString(), anyInt(), any(), anyInt())).
thenThrow(exception);

try {
myService.updateCommonErrorMessages();
fail();
}
catch (Exception ex) {
assertThat(ex).isEqualTo(exception);
}
finally {
restoreTemplate();
}

List<ErrorMessageInfo>  commonErrorMessagesAfter    =
    myService.
    getMyDAO().getCommonErrorMessages(MyService.CHANNEL_NAME);

assertThat(commonErrorMessagesBefore).isEqualTo(commonErrorMessagesAfter);

}

Note that although when I deploy to WebLogic, I define a normal connection-pooling transactional datasource, but in my integration test, the datasource uses a "org.springframework.jdbc.datasource.DriverManager DataSource".

What might I be missing?

Is it simply because the free Atomikos transaction manager isn't really transactional? What might I be missing?

David M. Karr
  • 14,317
  • 20
  • 94
  • 199

2 Answers2

1

The problem is with the mock setup. The update on the getOrigTemplate is getting called in the test method not when the update method on the mock is invoked.

when(template.update(eq(MyDAO.SQL_DELETE_CHANNEL), any())).
thenReturn(getOrigTemplate().update(MyDAO.SQL_DELETE_CHANNEL, MyService.CHANNEL_NAME));

you should be doing something like this to get the behavior you want.

when(template.update(eq(MyDAO.SQL_DELETE_CHANNEL), any())).thenAnswer(new Answer() {
     Object answer(InvocationOnMock invocation) {
         return getOrigTemplate().update(MyDAO.SQL_DELETE_CHANNEL,      
                      MyService.CHANNEL_NAME);
     }
 });
gkamal
  • 20,777
  • 4
  • 60
  • 57
  • Hmm, I was very hopeful this was right. It is right, but this is apparently not the only problem. After implementing this change, I stepped through the code in the debugger and verified that the "delete" call was only made in the "Answer" block, and I looked up the stack and saw that the TransactionInterceptor was there, so this appeared to be executed from within a transaction, but as soon as it executed the "update", I checked the database and the rows were gone. I let the test complete, and they were still gone. I think the transaction manager is still an issue. – David M. Karr Dec 10 '11 at 18:32
  • Can you check the dataSource configuration - you need to use a wrapped dataSource. Also are you using the same database type for both. Can you also verify with a DataSourceTransactionManager - that will help nail down if the problem is with Atomikos configuration. Right now there are two many variables in play - you will need to simplify - get it to work and add back one thing at a time. – gkamal Dec 11 '11 at 13:02
  • In my test contex, I'm using "org.springframework.jdbc.datasource.DriverManagerDataSource" for the datasources. I'm not sure what you mean by a "wrapped datasource". The database type is the same for test and appserver, in fact it's the same database and table for each. The test context is using a "org.springframework.transaction.jta.JtaTransactionManager", just like in the appserver context. Are you suggesting that I change the test context to instead use a "org.springframework.jdbc.datasource.DataSourceTransactionManager"? If so, I'll try that. – David M. Karr Dec 12 '11 at 15:04
  • yes - can you try the test with DataSourceTransactionManager - if that works then the test & spring transaction configuration is correct. We can then troubleshoot the Atomikos configuration. By the way why do you need Atomikos - do you need XA / JTA support. – gkamal Dec 12 '11 at 15:37
  • I have 7 different datasources that the application is using. It looks like DataSourceTransactionManager only works with a single datasource. Concerning why I'm using Atomikos, I'm not aware of any other option for a standalone integration test with multiple datasources (even if my two-phase commit doesn't need to cross multiple datasources). – David M. Karr Dec 13 '11 at 02:17
  • Ok makes sense. The DriverManagerDataSource won't work with atomikos. You will need to use the AtomikosDataSourceBean (http://www.atomikos.com/Documentation/ConfiguringJdbc#Using_AtomikosDataSourceBean). – gkamal Dec 13 '11 at 05:27
  • This appeared to be the missing link. There are a couple of properties mentioned in that doc that didn't match the actual properties, but those were minor obstacles. Once I changed all of my datasources to use that bean, this test case worked, and all of my other integration test cases worked also. – David M. Karr Dec 14 '11 at 19:07
-1

I use annotations for making test classes transactional. You may want to refer http://static.springsource.org/spring/docs/3.0.x/spring-framework-reference/html/testing.html#testing-tx for more info .

Aravind A
  • 9,507
  • 4
  • 36
  • 45