0

I have the following Spring MVC app stack:

Controller -> Service -> DAO (Repository) -> Entity -> MySql InnoDB

Transactions are configured to start from the Service layer using AOP xml config, and the app works perfectly:

<aop:config>
    <aop:advisor id="managerTx" advice-ref="txAdvice" pointcut="execution(* *..service.*Service.*(..))" order="1"/>
</aop:config>

<aop:aspectj-autoproxy proxy-target-class="true"/>

<tx:advice id="txAdvice">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

Now, I am writing Spring MVC tests using spring-mvc-test framework, UnitDB etc. The test class has the following config:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
        loader=WebContextLoader.class,
        locations={
            "classpath:spring/testContext.xml",
            "classpath:spring/testDispatcher-servlet.xml",
            "classpath:spring/security.xml"
        })
@TestExecutionListeners({ DependencyInjectionTestExecutionListener.class,
            DirtiesContextTestExecutionListener.class,
            TransactionalTestExecutionListener.class,
            DbUnitTestExecutionListener.class })
@TransactionConfiguration(transactionManager="transactionManager", defaultRollback=true)
@Transactional(propagation=Propagation.REQUIRED, rollbackFor={Exception.class})
@DatabaseSetup({"classpath:dbunit/initial-data.xml"})

The test looks something like this:

@Test
public void testSomeController() throws Exception {
    this.mockMvc.perform(get("/some/controller"));
}

Basically what I want is to test (use) the existing Spring AOP config inside the testing environment, initially setup some DB data, run the tests and rollback everything after the tests are completed.

In the above configuration, the tests are starting their own transaction which in the given propagation config is meeting another transaction at Service layer, started by AOP xml config and I get lock timeout error, because transaction started by test is waiting for the transaction started by AOP config at Service layer.

I am only writing tests and I shouldn't be modifying the actual code, only the test code. My task is to find a way to rollback all changes made by tests with the given configuration.

Any ideas appreciated.

EDIT

Here is the problematic case:

  1. test class starts one big new transaction with auto-rollback for all tests.
  2. dbunit fills the db with the initial data
  3. test launches controller test
  4. controller makes call to service layer e.g. save/update entity (inserted by DBUnit at the beginning of the test class), new transaction started here at service layer (configured by aop:config) but supported with the big transaction from the step #1
  5. boom, error happens: Lock wait timeout exceeded; try restarting transaction

I think that the data inserted by DBUnit is still not committed because it is in the same transaction. I need to find a way to insert initial data with DBUnit in a separate transaction but still to roll it back at the end of the test.

Amir Jamak
  • 351
  • 1
  • 2
  • 15
  • Can you enable DEBUG logging and provide a complete log via [pastebin.com](http://pastebin.com)? – Andrei Stefan Jun 24 '14 at 17:15
  • Yes I can, but I don't see a point for that. I am just asking for an abstract level explanation or idea, not a solution for some concrete bug. – Amir Jamak Jun 24 '14 at 19:42
  • It's not about some bug or something similar, it's just I don't know what's going on with your scenario from the details you provided and I'm thinking maybe some logs would shed some more light. If you don't see a point in sharing the logs, then don't do it and keep investigating the issue yourself. – Andrei Stefan Jun 24 '14 at 22:21
  • Hey @AndreiStefan thanks for your interest in this issue. I just think you didn't understand the point, I am asking for some architectural proposal, on higher level, not a specific debugging issue. – Amir Jamak Jun 25 '14 at 05:54
  • Then I think what you have already should work. I don't see anything obvious that shouldn't rollback the changes. – Andrei Stefan Jun 25 '14 at 05:58
  • Try removing the `DBUnit` for now and test with something else. Meaning insert the initial data in some other way, for example [Spring's embedded database support](http://docs.spring.io/spring/docs/4.0.4.RELEASE/spring-framework-reference/htmlsingle/#jdbc-embedded-database-support). [Here's one example](https://github.com/spring-projects/spring-framework/tree/master/spring-test/src/test/java/org/springframework/test/context/junit4/orm) with usage in a JUnit test from Spring's own test classes. After you remove DBUnit and test with something else, if it works then you know it's DBUnit's fault. – Andrei Stefan Jun 26 '14 at 07:33
  • 1
    I found various complaints [here](http://stackoverflow.com/questions/3813684/how-to-revert-the-database-back-to-the-initial-state-using-dbunit) and [here](http://mikael-amborn.blogspot.ro/2011/02/dbunit-tests-with-spring-and-nested.html) about DBUnit, so it might be something wrong on how you use it or something that needs cleaning up after usage. – Andrei Stefan Jun 26 '14 at 07:34

1 Answers1

0

I did 3 things that made it work:

  1. Changed DbUnitTestExecutionListener.class to TransactionDbUnitTestExecutionListener.class
  2. Made my test class extend AbstractTransactionalJUnit4SpringContextTests

    public class SomeControllerTest extends AbstractTransactionalJUnit4SpringContextTests

  3. Change my data source from org.apache.commons.dbcp.BasicDataSource to org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy as follows:

<bean id="dataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"> <constructor-arg ref="dbcpDataSource"/> </bean> <bean id="dbcpDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> <property name="maxActive" value="100"/> <property name="maxWait" value="1000"/> <property name="poolPreparedStatements" value="true"/> <property name="defaultAutoCommit" value="true"/> <property name="validationQuery" value="SELECT 1+1"/> <property name="testOnBorrow" value="true"/> </bean>

Hope it helps to someone

Amir Jamak
  • 351
  • 1
  • 2
  • 15