Transaction marked as rollback only: How do I find the cause
Asked Answered
S

9

133

I am having issues with committing a transaction within my @Transactional method:

methodA() {
    methodB()
}

@Transactional
methodB() {
    ...
    em.persist();
    ...
    em.flush();
    log("OK");
}

When I call methodB() from methodA(), the method passes successfuly and I can see "OK" in my logs. But then I get

Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:521)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:754)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:723)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:393)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:120)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
    at org.springframework.aop.framework.Cglib2AopProxy$DynamicAdvisedInterceptor.intercept(Cglib2AopProxy.java:622)
    at methodA()...
  1. The context of methodB is completely missing in the exception - which is okay I suppose?
  2. Something within the methodB() marked the transaction as rollback only? How can I find it out? Is there for instance a way to check something like getCurrentTransaction().isRollbackOnly()? - like this I could step through the method and find the cause.
Sollows answered 10/10, 2013 at 17:18 Comment(3)
related : #33278063Ogilvie
related: https://mcmap.net/q/169074/-could-not-commit-jpa-transaction-transaction-marked-as-rollbackonly/697313Mutinous
Interesting things to note is, if your database table does not exists, sometime this error will also be shown.Saccharine
S
86

I finally understood the problem:

methodA() {
    methodB()
}

@Transactional(noRollbackFor = Exception.class)
methodB() {
    ...
    try {
        methodC()
    } catch (...) {...}
    log("OK");
}

@Transactional
methodC() {
    throw new ...();
}

What happens is that even though the methodB has the right annotation, the methodC does not. When the exception is thrown, the second @Transactional marks the first transaction as Rollback only anyway.

Sollows answered 11/10, 2013 at 6:56 Comment(5)
The status of the transaction is stored in a thread local variable.When the spring intercepts methodC and sets the flag as rollback , your transaction is already marked for roll back. Any further suppression of exception will not help because when the final commit happens , you will get the errorShadowy
@Sollows Any way hypothetically if methodC have propagation=requires_new then methodB will not rollback?Jedidiah
methodC must be in different Spring bean/service or somehow accessed via Spring proxy. Otherwise Spring shall have no possibility to know about your exception. Only exception that passes through @Transactional annotation can mark transaction as rollback-only.Mutinous
That is not a solution. It is only about misunderstanding and incorrect use of Spring Transaction AOP mechanism. You should use Transactional annotation only when you are sure you need separate Transaction context or propagation for the actions you apply in that place. In any other case you can just correctly set-up Transaction Propagation. -1Alright
marks the first transaction. There is no first or second transaction - there is only one there. Because by default @Transactional's propagation is REQUIRED (which is treated as "execute within existing transaction if there is one")Prance
S
128

When you mark your method as @Transactional, occurrence of any exception inside your method will mark the surrounding TX as roll-back only (even if you catch them). You can use other attributes of @Transactional annotation to prevent it of rolling back like:

@Transactional(rollbackFor=MyException.class, noRollbackFor=MyException2.class)
Symons answered 11/10, 2013 at 6:3 Comment(11)
Well, I tried to use noRollbackFor=Exception.class, but it seems to have no effect – does it work for inherited exceptions?Breast
Yes it does. Looking at your own answer, that is right (you hadn't provided methodC in your first post). Both methodB and methodC use same TX and always the most specific @Transactional annotation is used, so when methodC throws the exception, surrounding TX will be marked as rollback-only. You can also use different propagation markers to prevent this.Symons
@Ean any exception inside your method will mark the surrounding TX as roll-back only Does this also applies for readOnly transactions?Mudra
@lolotron I have not noticed read-only TXs in particular but they should not be different. Read-only is something which is implemented in dialect level so it shouldn't impact the whole behavior.Symons
@lolotron @Ean I can confirm that it will indeed apply to a read-only transaction. My method was throwing an EmptyResultDataAccessException exception on a read-only transaction and I got the same error. Changing my annotation to @Transactional(readOnly = true, noRollbackFor = EmptyResultDataAccessException.class) fixed the problem.Rosenberger
This answer is wrong. Spring only knows about exceptions that pass through @Transactional proxy wrapper, i.e. uncaught. See other answer from Vojtěch for the full story. There could be nested @Transactional methods that can mark your transaction rollback-only.Mutinous
@YaroslavStavnichiy see my comment where I mentioned spring's propagations.Symons
@EanV you could clarify that "even if you catch them" means: exception thrown inside and catched outside of the method marked as Transactional.Banquer
noRollbackFor is work only if globalRollbackOnParticipationFailure=falseGermanize
@amir110 your comment should be highlighted as a one of the real possible and proper solutions. Only this way you can force Spring AOP Transaction handling mechanism to stop rolling back on Runtime Exceptions. Since every dev who use Transactional should know that by default Transaction handling mechanism DOES NOT ROLL BACK ON CHECKED EXCEPTIONS!!1 It rolls back only on Error or RuntimeException iheritance trees classes.Alright
This post does not answer the question. The question is how to find the cause, not what to do about it.Sarchet
S
86

I finally understood the problem:

methodA() {
    methodB()
}

@Transactional(noRollbackFor = Exception.class)
methodB() {
    ...
    try {
        methodC()
    } catch (...) {...}
    log("OK");
}

@Transactional
methodC() {
    throw new ...();
}

What happens is that even though the methodB has the right annotation, the methodC does not. When the exception is thrown, the second @Transactional marks the first transaction as Rollback only anyway.

Sollows answered 11/10, 2013 at 6:56 Comment(5)
The status of the transaction is stored in a thread local variable.When the spring intercepts methodC and sets the flag as rollback , your transaction is already marked for roll back. Any further suppression of exception will not help because when the final commit happens , you will get the errorShadowy
@Sollows Any way hypothetically if methodC have propagation=requires_new then methodB will not rollback?Jedidiah
methodC must be in different Spring bean/service or somehow accessed via Spring proxy. Otherwise Spring shall have no possibility to know about your exception. Only exception that passes through @Transactional annotation can mark transaction as rollback-only.Mutinous
That is not a solution. It is only about misunderstanding and incorrect use of Spring Transaction AOP mechanism. You should use Transactional annotation only when you are sure you need separate Transaction context or propagation for the actions you apply in that place. In any other case you can just correctly set-up Transaction Propagation. -1Alright
marks the first transaction. There is no first or second transaction - there is only one there. Because by default @Transactional's propagation is REQUIRED (which is treated as "execute within existing transaction if there is one")Prance
H
55

To quickly fetch the causing exception without the need to re-code or rebuild, set a breakpoint on

org.hibernate.ejb.TransactionImpl.setRollbackOnly() // Hibernate < 4.3, or
org.hibernate.jpa.internal.TransactionImpl() // as of Hibernate 4.3

and go up in the stack, usually to some Interceptor. There you can read the causing exception from some catch block.

Haveman answered 15/9, 2015 at 11:58 Comment(4)
In Hibernate 4.3.11, it is org.hibernate.jpa.internal.TransactionImplGudren
Very nice my friend!Kellda
Thanks! In newer versions of Hibernate (5.4.17) the class is org.hibernate.engine.transaction.internal.TransactionImpl and the method is setRollbackOnly.Stuckup
org.hibernate.jpa.internal.TransactionImpl.setRollbackOnly method for Hibernate 5.0.12Prance
I
13

I struggled with this exception while running my application.

Finally the problem was on the sql query. i mean that the query is wrong.

please verify your query. This is my suggestion

Insolation answered 10/2, 2016 at 10:52 Comment(1)
To clarify: if you 1. have an error in your sql syntax 2. are setup to rollback on exception 3. have readOnly transactions you will get this error because the sql syntax causes an exception that triggers a rollback which fails because you are in "readonly" mode.Downwash
I
10

Found a good explanation with solutions: https://vcfvct.wordpress.com/2016/12/15/spring-nested-transactional-rollback-only/

1) remove the @Transacional from the nested method if it does not really require transaction control. So even it has exception, it just bubbles up and does not affect transactional stuff.

OR:

2) if nested method does need transaction control, make it as REQUIRE_NEW for the propagation policy that way even if throws exception and marked as rollback only, the caller will not be affected.

Impedance answered 23/7, 2018 at 5:59 Comment(1)
Thank you! Requiring a NEW transaction in the nested method was the solution to my problemPaapanen
D
8

Look for exceptions being thrown and caught in the ... sections of your code. Runtime and rollbacking application exceptions cause rollback when thrown out of a business method even if caught on some other place.

You can use context to find out whether the transaction is marked for rollback.

@Resource
private SessionContext context;

context.getRollbackOnly();
Daffodil answered 10/10, 2013 at 20:7 Comment(3)
It seems to me I found the cause, but I don't understand why this is happening. An inner method throws an exception, which I catch, log and ignore. But the Transaction is marked as Rollback only anyway. How can I prevent it? I don't want the Transactions to be influenced by exceptions which I properly catch.Breast
Is SessionContext a standard class in Spring? It seems to me it is rather EJB3 and it is not contained in my Spring Application.Breast
My bad I missed the fact that it is about Spring. Anyway there should be something like TransactionAspectSupport.currentTransactionStatus().isRollbackOnly() available.Daffodil
I
6

There is always a reason why the nested method roll back. If you don't see the reason, you need to change your logger level to debug, where you will see the more details where transaction failed. I changed my logback.xml by adding

<logger name="org.springframework.transaction" level="debug"/>
<logger name="org.springframework.orm.jpa" level="debug"/>

then I got this line in the log:

Participating transaction failed - marking existing transaction as rollback-only

So I just stepped through my code to see where this line is generated and found that there is a catch block which did not throw anything.

private Student add(Student s) {
        try {
            Student retval = studentRepository.save(s);
            return retval;
        } catch (Exception e) {
            
        }
        return null;
    }
Invasive answered 13/1, 2021 at 16:18 Comment(1)
Thanks! This is the only answer to the question "how do I find the cause?".Manas
H
1

disable the transactionmanager in your Bean.xml

<tx:annotation-driven proxy-target-class="true" transaction-manager="transactionManager"/>
    <bean id="transactionManager"
        class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

comment out these lines, and you'll see the exception causing the rollback ;)

Heartstricken answered 5/7, 2015 at 7:39 Comment(0)
C
0

apply the below code in productRepository

@Query("update Product set prodName=:name where prodId=:id ") @Transactional @Modifying int updateMyData(@Param("name")String name, @Param("id") Integer id);

while in junit test apply below code

@Test
public void updateData()
{
  int i=productRepository.updateMyData("Iphone",102);

  System.out.println("successfully updated ... ");
  assertTrue(i!=0);

}

it is working fine for my code

Cockaleekie answered 7/7, 2019 at 18:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.