user689842
user689842

Reputation:

Why isn't Spring rolling back on RuntimeException?

I am trying to write a piece of code in which I can see an @Transaction method being rolled back upon a RuntimeException. That should be the expected default behaviour, but it is not what I am seeing. Any ideas why?

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:mrpomario/springcore/jdbc/jdbc-testenv-config.xml")
@Transactional // Will rollback test transactions at the end
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class TransactionalTest
{
    @Autowired
    FeedManagerOne feedManagerOne;

    @Test
    public void test_RuntimeExceptions_Rollback_Behaviour(){
         Feed bogus = new Feed("B", "B", false);

         assertFalse(feedManagerOne.exists(bogus));

         try {
              feedManagerOne.createFeedAndThrowRuntimeException(bogus);
         }  catch (RuntimeException e) { }

         // WRONG! feedManagerOne.exists(bogus) SHOULD return false, but returns true.
         assertFalse(feedManagerOne.exists(bogus));
    }

}

My Service:

@Service
public class FeedManagerOne {
    @Autowired
    JdbcTemplate jdbcTemplate;

    @Transactional(readOnly = true)
    public boolean exists(Feed feed) {
        String query = "SELECT COUNT(*) FROM feed WHERE name = ? AND url = ? AND is_active = ?";
        int total = jdbcTemplate.queryForInt(query, feed.getName(), feed.getUrl(), feed.isActive());
        boolean found = (total == 1);

        return found;
    }

    @Transactional
    public boolean createFeedAndThrowRuntimeException(Feed feed) {
        String query = "INSERT INTO feed (name, url, is_active) values (?, ?, ?)";
        int rowsChanged = jdbcTemplate.update(query, feed.getName(), feed.getUrl(), feed.isActive());
        boolean created = (rowsChanged == 1);

        if (true)
        {
            throw new RuntimeException();
        }

        return created;
    }
}

This is how I define my TransactionManager:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
    <jdbc:embedded-database id="dataSource" type="H2">
    <jdbc:script location="mrpomario/springcore/jdbc/testdb/schema.sql"/>
    <jdbc:script location="classpath:mrpomario/springcore/jdbc/testdb/test-data.sql"/>
</jdbc:embedded-database>

Upvotes: 2

Views: 2462

Answers (1)

axtavt
axtavt

Reputation: 242786

It's expected behaviour.

When you throw an exception (that should cause rollback) from @Transactional method, Spring marks transcation to be rolled back at its end. So, if you throw an exception from the top-most @Transactional method in the call stack, transaction will be rolled back immediately.

But in your case your test method is @Transactional as well, therefore you have a single transaction that spans the whole test method. It means that despite the fact that transaction is marked for rollback after calling createFeedAndThrowRuntimeException(), it's not rolled back until the end of the test method, therefore the second call of exists() can observe the changes.

So, if you want to see the rollback you need to make your test method non-transactional.

Also I don't see <tx:annotation-driven/> in your configuration.

Upvotes: 5

Related Questions