Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Could not commit JPA transaction: Transaction marked as rollbackOnly

I'm using Spring and Hibernate in one of the applications that I'm working on and I've got a problem with handling of transactions.

I've got a service class that loads some entities from the database, modifies some of their values and then (when everything is valid) commits these changes to the database. If the new values are invalid (which I can only check after setting them) I do not want to persist the changes. To prevent Spring/Hibernate from saving the changes I throw an exception in the method. This however results in the following error:

Could not commit JPA transaction: Transaction marked as rollbackOnly 

And this is the service:

@Service class MyService {    @Transactional(rollbackFor = MyCustomException.class)   public void doSth() throws MyCustomException {     //load entities from database     //modify some of their values     //check if they are valid     if(invalid) { //if they arent valid, throw an exception       throw new MyCustomException();     }    } } 

And this is how I invoke it:

class ServiceUser {   @Autowired   private MyService myService;    public void method() {     try {       myService.doSth();     } catch (MyCustomException e) {       // ...     }           } } 

What I'd expect to happen: No changes to the database and no exception visible to the user.

What happens: No changes to the database but the app crashes with:

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

It's correctly setting the transaction to rollbackOnly but why is the rollback crashing with an exception?

like image 639
user3346601 Avatar asked Aug 15 '14 07:08

user3346601


People also ask

What is a rollback only transaction?

As a result of an error condition or exception, an application can determine that the current transaction should be rolled back. However, the application should not attempt to rollback the transaction directly.

What is JPA transaction?

An object level transaction is one in which a set of changes made to a set of objects are committed to the database as a single unit. JPA provides two mechanisms for transactions. When used in Java EE JPA provides integration with JTA (Java Transaction API).

What is rollback exception in Java?

RollbackException exception is thrown when the transaction has been marked for rollback only or the transaction has been rolled back instead of committed. This is a local exception thrown by methods in the UserTransaction , Transaction , and TransactionManager interfaces.

What is read only transaction spring?

In a Spring application, the web @Controller calls a @Service method, which is annotated using the @Transactional annotation. By default, Spring transactions are read-write, but you can explicitly configure them to be executed in a read-only context via the read-only attribute of the @Transactional annotation.


1 Answers

My guess is that ServiceUser.method() is itself transactional. It shouldn't be. Here's the reason why.

Here's what happens when a call is made to your ServiceUser.method() method:

  1. the transactional interceptor intercepts the method call, and starts a transaction, because no transaction is already active
  2. the method is called
  3. the method calls MyService.doSth()
  4. the transactional interceptor intercepts the method call, sees that a transaction is already active, and doesn't do anything
  5. doSth() is executed and throws an exception
  6. the transactional interceptor intercepts the exception, marks the transaction as rollbackOnly, and propagates the exception
  7. ServiceUser.method() catches the exception and returns
  8. the transactional interceptor, since it has started the transaction, tries to commit it. But Hibernate refuses to do it because the transaction is marked as rollbackOnly, so Hibernate throws an exception. The transaction interceptor signals it to the caller by throwing an exception wrapping the hibernate exception.

Now if ServiceUser.method() is not transactional, here's what happens:

  1. the method is called
  2. the method calls MyService.doSth()
  3. the transactional interceptor intercepts the method call, sees that no transaction is already active, and thus starts a transaction
  4. doSth() is executed and throws an exception
  5. the transactional interceptor intercepts the exception. Since it has started the transaction, and since an exception has been thrown, it rollbacks the transaction, and propagates the exception
  6. ServiceUser.method() catches the exception and returns
like image 102
JB Nizet Avatar answered Oct 05 '22 07:10

JB Nizet