Question

I want to test a service-method that inserts data into a table by calling a DAO in a loop. The service-method is annotated with

@Transactional(propagation = Propagation.REQUIRES_NEW)

The unit-test calls the service-method and is annotated with

@Transactional

Now I would like to tell the transaction that it always should do a rollback at the end. I don't want to clean up the db manually after the testruns.

@Rollback and EntityManager.getTransaction().setRollbackOnly() does'nt work. I think the reason is that the annotation and setRollbackOnly() are only applied on the Transaction that is created by the test-method and not on the transaction that is created by the service-method.

Does anyone know how to solve that?

No correct solution

OTHER TIPS

I don't think it's possible to easily rollback a REQUIRES_NEW transaction. SpringTest starts a transaction and it can rollback the transaction that it started. But not the transactions started inside.

So you either may fall back to REQUIRED or write your tests to work fine even if they commit. If you choose the latter, you can achieve test isolation via randomization.

You can probably create a controllable mock implementation of org.springframework.transaction.PlatformTransactionManager delegating to the real one with only single difference that it would optionally mark a new transaction as read-only. Something like that if using java-based context configuration:

...
public class DaoTest {
...
@Autowired
Dao _dao;
@Autowired
MockPlatformTransactionManager _transactionManager;
...
@Test
@Transactional
public void testSomeAction() throws Exception
{
    try (AutoCloseable ignored = _transactionManager.withRollBack())
    {
        _dao.someAction();
    }
}
...
interface MockPlatformTransactionManager extends PlatformTransactionManager
{
    AutoCloseable withRollBack();
}
...
// Somewhere in the @Configuration class
@Bean
MockPlatformTransactionManager transactionManager()
{
    // an instance of real TM is adapted to become a MockPlatformTransactionManager 
    return new MockPlatformTransactionManager()
    {
        private final PlatformTransactionManager _delegate = 
            // TODO: same transaction manager as before
            new DataSourceTransactionManager(dataSource());
        private boolean shouldRollBack = false;

        @Override
        public TransactionStatus getTransaction(final TransactionDefinition definition)
            throws TransactionException
        {
            final TransactionStatus transaction = _delegate.getTransaction(definition);
            if (shouldRollBack)
                transaction.setRollbackOnly();
            return transaction;
        }

        @Override
        public void commit(final TransactionStatus status) throws TransactionException
        {
            _delegate.commit(status);
        }

        @Override
        public void rollback(final TransactionStatus status) throws TransactionException
        {
            _delegate.rollback(status);
        }

        @Override
        public AutoCloseable withRollBack()
        {
            shouldRollBack = true;
            return new AutoCloseable()
            {
                @Override
                public void close() throws Exception
                {
                    shouldRollBack = false;
                }
            };
        }
    };
}

If you use a PlatformTransactionManager implementation that permits to set the UserTransaction (like org.springframework.transaction.jta.JtaTransactionManager) you can instantiate an UserTransaction implementation which commit method does a rollback.

import com.atomikos.icatch.jta.UserTransactionImp;

@Bean
public UserTransaction myUserTransaction() {
    final UserTransactionImp userTransactionImp = new UserTransactionImp() {            

        @Override
        public void commit() throws javax.transaction.RollbackException, javax.transaction.HeuristicMixedException,
                javax.transaction.HeuristicRollbackException, javax.transaction.SystemException, java.lang.IllegalStateException,
                java.lang.SecurityException {
            rollback();
        }
    };

    return userTransactionImp;
}

And then, in your PlatformTransactionManager bean:

@Bean
public PlatformTransactionManager transactionManager(
        @Qualifier("myUserTransaction") UserTransaction myUserTransaction,
        @Qualifier("myTransactionManager") TransactionManager myTransactionManager
) {

    final JtaTransactionManager jtaTm = new JtaTransactionManager();

    jtaTm.setTransactionManager(myTransactionManager);
    jtaTm.setUserTransaction(myUserTransaction);

    return jtaTm;
}

I've also made my dao does not make any flush.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top