问题
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?
回答1:
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.
回答2:
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
.
来源:https://stackoverflow.com/questions/1701750/how-to-prevent-jpa-from-rolling-back-transaction