Reputation:
I am seeing a weird behaviour that is not consistent with the documentation. This is what I am doing: from my Test class I invoke a @Transactional Service that creates a row in the DB and then invokes another @Transactional Service (marked with REQUIRES_NEW) that throws a RunTimeException. I would expect to see my record at the end in my Test, but I am not . Ideas?
My Test class:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"classpath:**/jdbc-beans.xml","classpath:**/jdbc-infrastructure.xml","classpath:**/jdbc-infrastructure-test.xml" })
@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)
public class TransactionalTest {
@Autowired
FeedManagerOne feedManagerOne;
@Test
public void test_propagation_REQUIRES_to_REQUIRES_NEW() {
Feed anotherFeed = new Feed("AnotherRSS", "http://www.anotherfeedlink.com", true);
assertFalse(feedManagerOne.exists(anotherFeed)); // EVALUATES TO FALSE
try {
feedManagerOne.createFeed(anotherFeed, true);
} catch (RuntimeException e) {
e.printStackTrace();
}
// EVALUATES TO FALSE. WRONG! IT SHOULD BE TRUE.
assertTrue(feedManagerOne.exists(anotherFeed));
}
}
Service One:
@Service
public class FeedManagerOne {
@Autowired
FeedManagerTwo feedManagerTwo;
@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 createFeed(Feed feed, boolean invokeFeedManagerTwo) {
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);
// WHEN THE THE FOLLOWING METHOD THROWS A RUNTIME EXCEPTION,
// THE JUST CREATED RECORD WILL BE LOST FROM THE DB (SEE TEST)
feedManagerTwo.throwRuntimeExceptionFromRequiresNew();
return created;
}
}
Service Two
@Service
public class FeedManagerTwo {
@Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED)
public void throwRuntimeExceptionFromRequiresNew() {
if (true) {
throw new RuntimeException("From REQUIRES_NEW");
}
}
}
Upvotes: 2
Views: 1322
Reputation: 691635
It's completely consistent with the documentation. The inner service throws an exception, so its transaction is rolled back. Then the exception propagates to the outer service, which causes the outer transaction to also rollback.
Catch the exception inside the outer service, and it won't rollback anymore.
Upvotes: 3