How to prevent JPA from rolling back transaction?
Asked Answered
S

2

9

Methods invoked:
1. Struts Action
2. Service class method (annotated by @Transactional)
3. Xfire webservice call

Everything including struts (DelegatingActionProxy) and transactions is configured with Spring.

Persistence is done with JPA/Hibernate.

Sometimes the webservice will throw an unchecked exception. I catch this exception and throw a checked exception. I don't want the transaction to roll back since the web service exception changes the current state. I have annotated the method like this:

@Transactional(noRollbackFor={XFireRuntimeException.class, Exception.class})
public ActionForward callWS(Order order, ....) throws Exception
  (...)
  OrderResult orderResult = null;

  try {
    orderResult = webService.order(product, user)
  } catch (XFireRuntimeException xfireRuntimeException) {
    order.setFailed(true);
    throw new WebServiceOrderFailed(order);
  } finally {
    persist(order);
  }
}

I still get this exception:

org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly

When I try to reproduce this with junit, the transaction isn't marked for roll back and it's still possible to commit the transaction.

How do I make Spring not to roll back the transaction?

Spenserian answered 9/11, 2009 at 15:28 Comment(0)
S
9

Managed to create a test case for this problem:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"file:web/WEB-INF/spring/applicationContext.xml",
        "file:web/WEB-INF/spring/services.xml"})
@Transactional
public class DoNotRollBackTest {
    @Autowired FakeService fakeService;

    @Test
    @Rollback(false)
    public void testRunXFireException() {
        fakeService.doSomeTransactionalStuff();
    }
}

FakeService:

@Service
public class FakeService {
    @Autowired private EcomService ecomService;
    @Autowired private WebService webService;

    @Transactional(noRollbackFor={XFireRuntimeException.class})
    public void doSomeTransactionalStuff() {
        Order order = ecomService.findOrderById(459);

        try {
            webService.letsThrowAnException();
        } catch (XFireRuntimeException e) {
            System.err.println("Caugh XFireRuntimeException:" + e.getMessage());
        }

        order.setBookingType(BookingType.CAR_BOOKING);
        ecomService.persist(order);
    }
}

WebService:

@Transactional(readOnly = true)
public class WebService {
    public void letsThrowAnException() {
        throw new XFireRuntimeException("test!");
    }
}

This will recreate the rollback-exception.

Then I realized that the transaction is probably being marked as rollbackOnly in WebService.letsThrowAnException since WebService is also transactional. I moved to annotation:

@Transactional(noRollbackFor={XFireRuntimeException.class})
    public void letsThrowAnException() {

Now the transaction isn't being rolled back and I can commit the changes to Order.

Spenserian answered 10/11, 2009 at 10:41 Comment(0)
S
3

You must not throw an exception where Spring can see it. In this case, you must not throw WebServiceOrderFailed(). The solution is to split the code into two methods. The first method does the error handling and returns the exception, the outer method creates the transaction.

[EDIT] As for noRollbackFor: Try to replace Exception.class with WebServiceOrderFailed.class.

Succumb answered 9/11, 2009 at 15:35 Comment(5)
This is incorrect. noRollbackFor checks specified exception class and all its subclasses: static.springsource.org/spring/docs/2.5.x/api/org/… Additionally, by default checked exceptions will NOT trigger the rollback: static.springsource.org/spring/docs/2.5.x/reference/…Rambutan
This doesn't explain why the code above rolls back on WebServiceOrderFailed.Succumb
My guess is that WebServiceOrderFailed is a RuntimeException and the code above (noRollbackFor={..., Exception.class}) can't have any effect since Exception is handled specially (otherwise, the inheritance code would also ignore RuntimeException since it extends Exception).Succumb
Thanks for your input. You guys pointed me in the right direction! (WebServiceOrderFailed extends Exception, it's a checked exception)Spenserian
Kindly help as I face the same issue #54343637Wifely

© 2022 - 2024 — McMap. All rights reserved.