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>
It’s expected behaviour.
When you throw an exception (that should cause rollback) from
@Transactionalmethod, Spring marks transcation to be rolled back at its end. So, if you throw an exception from the top-most@Transactionalmethod in the call stack, transaction will be rolled back immediately.But in your case your test method is
@Transactionalas 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 callingcreateFeedAndThrowRuntimeException(), it’s not rolled back until the end of the test method, therefore the second call ofexists()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.