maqjav
maqjav

Reputation: 2434

Avoid Transaction rollback in Spring

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?

Upvotes: 4

Views: 9552

Answers (2)

Essex Boy
Essex Boy

Reputation: 7950

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.

Upvotes: 6

Ye Win
Ye Win

Reputation: 2098

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.

Upvotes: 1

Related Questions