How to prevent JPA from rolling back transaction? - java

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?

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.

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.

Related

UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only meanwhile the dontRollbackOn was already set

I have the Controller-Service-Dao pattern on my back-end service, it's like that:
Controller
#RestController
public class A {
#Autowired
B b;
#GetMapping("/path")
public ResponseEntity methodA() throws Exception {
return ResponseEntity.ok(b.methodB());
}
}
Service layer 1
#Service
public class B {
#Autowired
E e;
#Transactional(dontRollbackOn = {Exception.class})
public Map methodB() throws Exception {
return e.methodE();
}
}
2.a. Service layer II
#Service
public class E {
#Autowired
C c;
#Transactional(dontRollbackOn = {Exception.class})
public Map methodE() throws Exception {
List<D> ds = c.inquiry();
for (D d : ds) {
try { // process every d here }
catch(Exception ex) {d.setStatus("FAIL!");}
c.save(d);
}
return new HashMap(){{ put("deleted_rows", c.delete()); }};
}
}
Dao
#Component
public class C {
public List<D> inquiry() {
Session session = sessionFactory.getCurrentSession();
return session.createQuery("from D").list();
};
public void save(D d){
Session session = sessionFactory.getCurrentSession();
session.save(d);
};
public int delete() {
sessionFactory.getCurrentSession().createQuery("delete from D where createdDate < :today")
.setParameter("today", today).executeUpdate();
}
}
The above is the simpler version of my real code. But I wonder what should I do to prevent this error : org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only
When I see the log, there's indeed an error caught in the try-catch block inside the method E.methodE(). I also saw that the hibernate log of succeeded deleting the rows. But the issue is it got rolled back at last just when committing. What should I do to repair this? Is there mistake in this transactional annotation usage?
In JPA domain almost all runtime exceptions cause marking current transaction as rollback-only, please check for example ExceptionConverterImpl#convert. The reason for that is very obvious: if something goes wrong that means persistence context is not in sync with DB anymore (we performed changes in persistence context, but failed to persist them), and the most straightforward "compensation" there is to rollback transaction.
Spring's Transactional#noRollbackFor does not work "as expected" for the most exceptions raised in JPA domain, and the only option is to write a code in more accurate way.

Spring transaction rolled back on RuntimeException

I have a transactional method and I'd like to call an other method which may throw a RuntimeException.
The problem is that the transaction gets marked as rollbackOnly when the exception is thrown.
The other method call itself is in a try-catch block, but I think the transaction gets marked when the other method returns by throwing an exception.
Example:
MyService.java
#Service
public class MyService {
#Autowired
private MyUtils utils;
#Autowired
private MyCrudRepository repository;
#Transactional
public void publicMethod() {
try {
utils.otherPublicMethod();
} catch (Exception ex) {
ex.printStackTrace();
}
// RollbackException: Transaction marked as rollbackOnly
// Even though I caught the exception from the method call itself
repository.save(new MyEntity());
}
}
MyUtils.java
#Component
public class MyUtils {
// Does not use transactions, repositories
// But I think it inherits the transaction and marks it as rollbackOnly
public void otherPublicMethod() {
// Maybe this is seen as an uncaught exception
throw new RuntimeException();
}
}
Edit:
I don't think this is a duplicate of Does Specifying #Transactional rollbackFor Also Include RuntimeException, because the exception is eventually caught.
The problem may be similar, as it also involves transactions and rollbacks.
Annotation #Transactional has parameters such as rollbackFor and no-rollback-for with which you can say which exceptions will cause or will not cause rollback. For instance you can write:
#Transactional(rollbackFor = {RuntimeException.class})
Which will cause rollback upon RuntimeException. However, I believe that RuntimeException causes rollback by default. So you probably have to specify this exception in no-rollback-for clause.
For details see Transaction Management and look for "16.5.5 settings" paragraph

What is the difference between a method with #Transactional(propagation = Propagation.SUPPORTS) and method without #Transactional?

What is the difference between method with
#Transactional(propagation = Propagation.SUPPORTS)
and a method without #Transactional?
For example:
public class TEst {
#Transactional
public void methodWithTransaction1(){
methodWithSupportsTransaction();
}
#Transactional
public void methodWithTransaction2(){
methodWithoutTransactional();
}
#Transactional(propagation = Propagation.SUPPORTS)
public void methodWithSupportsTransaction(){
}
public void methodWithoutTransactional(){
}
}
Except for the slight difference indicated in the javadoc regarding synchronization, a difference between both is that a transactional proxy intercepts the method call if the method is annotated with Transactional, and will mark the current transaction, if any, as rollbackOnly if a runtime exception is thrown from that method.
So, let's take an example:
public class A {
#Autowired B b;
#Transactional
public void foo() {
try {
b.bar();
}
catch (RuntimeException e) {
// ignore
}
...
}
}
public class B {
// #Transactional(propagation = Propagation.SUPPORTS)
public void bar() {
throw new RuntimeException();
}
}
Calling a.foo() will start a transaction if none exists yet (propagation REQUIRED). Then b.bar() will be called and will throw an exception. The exception is caught by a.foo(), which continues executing as if nothing happened. At the end of a.foo(), the transaction will be committed successfully.
Now let's uncomment the Transactional annotation on b.bar(). Calling a.foo() will start a transaction if none exists yet (propagation REQUIRED). Then b.bar() will be called and will throw an exception. This exception will be "intercepted" by the transactional proxy around B, which will mark the transaction to rollbackOnly. Then the exception will propagate to A. a.foo(). The exception is caught by a.foo(), which continues executing as if nothing happened. At the end of a.foo(), the transaction will be committed, but that commit will fail because the transaction has already been marked as rollbackOnly. The caller of a.foo() will get a TransactionSystemException.
Spring supports two types of transaction management programmatic and declarative.
Programmatic transaction management: In this manner transactions need to be handled by us. For example-
EntityTransaction tran = entityManager.getTransaction();
try {
tran.begin();
methodWithoutTransactional();
tran.commit();
} catch(Exception ex) {
tran.rollback();
throw ex;
}
Declarative transaction management: In this manner, we can separate out transaction management code from our business logic, simply by using annotation or xml based configuration. Which you've already done in example code-
#Transactional
public void methodWithTransaction1(){
methodWithSupportsTransaction();
}
For #Transactional annotation if we do not define the propagation type, PROPAGATION_REQUIRED will be applied by default. You can find the documentation here.

How to return different value, when transaction rollback?

I use ssh framework to develop a web application.
There is an example of my transaction.
#Transactional
public StudentEntity addStudent(StudentEntity studentEntity) {
return studentDAO.save(studentEntity);
}
Now, I want to return null when exception is thrown and then transaction rollback.
In general it is not advised to return null.
If you anticipate any Exception from your logic you should inform the caller via throws clause so that they are prepared for such scenarios.
Regarding rollback you should consider below update to your #Transactional annotation
#Transactional(rollbackFor=Exception.class)
Do note that this will rollback transaction after throwing any exception.
To rollback transaction programatically take a look at TransactionAspectSupport class.
#Transactional
public StudentEntity addStudent(StudentEntity studentEntity) {
try {
return studentDAO.save(studentEntity);
}
catch(Exception ex) {
//set transaction for rollback.
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
You could do it declarative manner
#Transactional(rollbackFor={SomeSpecificException.class, SomeOtherSpecificException.class})

Spring transactions with caught Exceptions

Recently, I've been working with Spring boot + spring data jpa + hibernate. I faced one problem with spring transactions. Here is my service class and two questions:
#Transactional
#Service
class MyService {
#Autowired
private MyRepository myRep;
public void method_A() {
try {
method_C();
.....
method_B();
} catch(Exception e) {}
}
public void method_B() {
Entity e = new Entity();
e.set(...);
myRep.save(e);
}
public void method_C() throws Exception {
....
}
}
1.If method method_C() throws an Exception and I want to catch it and log it, the transaction is not rollbacked in method method_B(), because the Exception does not reach Spring framework. So how should I do in order to catch Exceptions from method_C() and at the same time do not lose capability of method method_B() be rollbacked?
2.Consider new method method_A().
public void method_A() {
for(...) {
...
...
method_B();
}
}
I want invoke method_B() in a loop. If an exception occurs in a method_B() I want transaction of method_B() be rollbacked but method_A() should not exit and the loop should continue excuting. How can I achieve this?
I solved my 2 problems this way: created another #Service class and moved method_B() into it. I've annotated this class as #Transactional. Now the method method_A() looks like this:
public void method_A() {
for(...) {
...
try {
anotherService.method_B();
} catch (Exception e) {
logger.error(...);
}
}
}
If RuntimeException occurs in the method_B() method, the exception is propertly logged, transaction of method_B() is rollbacked and the loop continuous. Thanks everybody for responses.
Instead of throwing exceptions do the following. (return error code).
Update: I read your question after posting. if you call method_b from method_A both are under same transaction. Unfortunately you cannot rollback the method_b changes alone. Spring considers it as one transaction if they are all under one service class. (all methods).
One thing you can try is the following.
request to--> Controller() ---> (spring opens transaction) service_method_a(); (spring closes transaction)
Controller() ---> (spring opens transaction) service_method_c(); (spring closes transaction)
Controller() ---> (spring opens transaction) service_method_b(); (spring closes transaction)
return <--
I hope it makes sense
Each of your methods a,b,c throw exceptions if it likes to be rolledback.
Update:
another approach. This one is much better.
If each of your method are in a different service then you can use the following annotations of the spring to run each of the method in a different transaction boundaries
p v serviceA{
#transactional
method_a(){
serviceb.method_b();
}
}
p v serviceB{
#Transactional(propagation=Propagation.REQUIRED)
method_b(){
}
}
more on it here
Spring transactional story here . Read the points below this article. Those are most important when developing the spring transactional app.

Categories

Resources