6
votes

I'm using Hibernate + spring + @Transactional annotations to handle transactions in my application.

Transaction manager is declared as follows:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="sessionFactory" ref="sessionFactory"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

This works well in most cases, but I've found a problem where I have 2 methods, both annotated @Transactional:

package temp;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

public class OuterTestService {
    @Autowired
    private InnerTestService innerTestService;

    @Transactional
    public void outerMethod() {
        try {
            innerTestService.innerTestMethod();
        } catch (RuntimeException e) {
            // some code here
        }
    }
}

and

package temp;

import org.springframework.transaction.annotation.Transactional;

public class InnerTestService {

    @Transactional
    public void innerTestMethod() throws RuntimeException {
        throw new RuntimeException();
    }
}

When I invoke OuterTestService#outerMethod(), I get an exception

org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

As there is only one transaction (no nested transactions), the whole outerTestMethod()'s transaction is marked as rollback-only.

I've found that I can easily overcome this using noRollbackFor:

package cz.csas.pdb.be.service.tempdelete;

import org.springframework.transaction.annotation.Transactional;

public class InnerTestService {

    @Transactional(noRollbackFor = RuntimeException.class)
    public void innerTestMethod() throws RuntimeException {
        throw new RuntimeException();
    }
}

But this has to be explicitly used on every method. Because this error is not raised during tests (which are rollbacked), this is not acceptable.

My question -- is there a way to automatically (eg. not explicitly for every method) set that transactions are rolled back only when an exception is raised from method which started the transaction (in this case, the outerTestMethod())?

2
Not sure what you are expecting,check this (but dont think its good idea)static.springsource.org/spring/docs/2.0.0/api/org/…blob
I expect to be able to configure the transaction manager such that it will not rollback (or mark as rollback-only) transaction when an exception occurs except if it occurs in the method, which started (or was the cause to start) the transaction.Ondrej Skalicka
Having a @Transactional method call another one seems like bad news to me, as if you're not really clear on what the scope of the transaction is. I'd fix that first, one way or another.Donal Fellows
Well, I need to have @Transactional on both, because each can be called directly and I need them to be in transaction.Ondrej Skalicka

2 Answers

11
votes

Create another annotation, e.g. @NoRollbackTransactional, something like:

@Transactional(noRollbackFor = RuntimeException.class)
public @interface NoRollbackTransactional {
}

And use this on your methods. On the other hand I agree with Donal's comment, you should revise your transaction scopes, imho it is generally not a good idea to call @Transactional from another @Transactional.

5
votes

You may also need to turn off globalRollbackOnParticipationFailure.

I had this problem when I had an independent transaction within a surrounding transaction. When the independent transaction failed, it also failed the surrounding transaction.

Turning off the participation flag solved this for me:

<bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
    <property name="globalRollbackOnParticipationFailure" value="false" />
    <property name="sessionFactory" ref="sessionFactory" />
</bean>