Reputation:
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
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