user689842
user689842

Reputation:

Propagation with REQUIRES_NEW rolling back the outter transaction too. Why?

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

Answers (1)

JB Nizet
JB Nizet

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

Related Questions