4

Assuming I have the next code:

@Autowired
private IManager1 manager1;

@Autowired
private IManager2 manager2;

@Autowired
private IManager3 manager3;

@Transactional
public void run() {
     manager1.doStuff();
     manager2.registerStuffDone();

     manager3.doStuff();
     manager2.registerStuffDone();

     manager1.doMoreStuff();
     manager2.registerStuffDone();
}

If any exception is launched I want to rollback everything done by the "doStuff()" methods, but I don't want to rollback the data recorded by the "registerStuffDone()" method.

I've been reading the propagation options for @Transactional annotation, but I don't understand how to use them properly.

Every manager internally uses hiberante to commit the changes:

@Autowired
private IManager1Dao manager1Dao;

@Transactional
public void doStuff() {
    manager1Dao.doStuff();
}

Where the dao looks like this:

@PersistenceContext
protected EntityManager entityManager;

public void doStuff() {
    MyObject whatever = doThings();
    entityManager.merge(whatever);
}

This is my applicationContext configuration:

<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="dataSource" ref="dataSourcePool" />
    <property name="jpaVendorAdapter">
        <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"/>
    </property>
</bean>

<bean id="entityManager" class="org.springframework.orm.jpa.support.SharedEntityManagerBean">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
    <property name="entityManagerFactory" ref="entityManagerFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager"/>

Ideas?

2 Answers 2

6

You need 2 transactions, one for the stuff to be committed and one for the stuff to be rolled back.

@Transactional(propagation = Propagation.REQUIRES_NEW, noRollbackFor={Exception1.class, Exception2.class})
public void registerStuffDone()() {
   //code
}

Your run method will then use the first transaction and that will be rolled back, but the registerStuffDone method will start a second transaction which will be commited.

Sign up to request clarification or add additional context in comments.

5 Comments

I tried Propagation.REQUIRES_NEW but it keeps using the same transaction. I added my applicationContext configuration, just in case there is something affecting to this issue.
I'm sure this will work. The config looks right to me, why don't you switch the logging up and run a unit test?
You were right!, that made the trick. In the real project there is another manager in between the main app and the example managers. I was trying to add the @Transactional(propagation = Propagation.REQUIRED_NEW) to this intermediate method, however like my manager2 had already the annotation @Transactional, this was overriding everything defined in the parent. Silly of me I didn't check that before! Thanks.
Propagation.NOT_SUPPORTED is an option also if the inner method call/transaction does not persist anything. In my case, my inner transaction is calling an optional outside service and not interacting with my db.
@Transactional(value=Transactional.TxType.REQUIRES_NEW, dontRollbackOn={Exception1.class})
1

You are using declarative transaction and want to control like program sense. For this reason, you need more practice and deep understanding about Spring transaction definition such as PROPAGATION, ISOLATION etc...

Programmatic transaction management: This means that you have manage the transaction with the help of programming. That gives you extreme flexibility, but it is difficult to maintain.
Vs
Declarative transaction management: This means you separate transaction management from the business code. You only use annotations or XML based configuration to manage the transactions.

Perhaps, alternative way for your questions by Programmatic transaction management.

/** DataSourceTransactionManager */
    @Autowired
    private PlatformTransactionManager txManager;

    public void run() {
    try {

         // Start a manual transaction.
         TransactionStatus status = getTransactionStatus();

         manager1.doStuff();
         manager2.registerStuffDone();

         manager3.doStuff();
         manager2.registerStuffDone();

         manager1.doMoreStuff();
         manager2.registerStuffDone();

        //your condition 
        txManager.commit(status);
        //your condition 
        txManager.rollback(status);

         } catch (YourException e) {

        }       
    }    

/**
     * getTransactionStatus
     *
     * @return TransactionStatus
     */
    private TransactionStatus getTransactionStatus() {
        DefaultTransactionDefinition dtd = new DefaultTransactionDefinition();
        dtd.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        dtd.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
        dtd.setReadOnly(false);

        return txManager.getTransaction(dtd);
    }

Note: It does not mean you need to always use one approach like Programmatic transaction management. I prefer mixed approach. Please use easy way like Declarative transaction for simple database services, otherwise, just control with Programmatic transaction in your services will save your logic easily.

2 Comments

Thank you for your answer, it was very instructive. However I could fix it with annotations at the end.
Glad to hear!Great!

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.