Scorpio
Scorpio

Reputation: 2327

Forced rollback of Transaction causes Nested Transactions also to roll back?

Situation - Story time:
I "inherited" a program, a rather simple webservice for accessing a databse. This program had a flaw somewhere: It tried to update a table it had no update grant for. The program has only the right to update a Database Queue (Oracle), to save information who accessed what. This was undesired behaviour, and by now I fixed it. Note: This is not relevant to this question per se, its just the cause that led me to this question.

The program uses Spring + Hibernate for managing and accessing the data and the transactions.

Because the program is in high demand and the error was deemed intolerable, I had the quick idea to add a hotfix, simply to force a rollback on each transaction until I find the part of the software that actually manipulates data it should not manipulate.

The software uses 2 @Transactional annotations. One over the whole process, and another on the part thats writing the log data. This second one was used with the Requires_New propagation setting. As far as I understood it, this second one would always create a new Transaction and flush it when its span (in this case: one method) was over.

I then added a direct Rollback-Statement TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); after the nested transaction was already over, to rollback the outer transaction (therefor undoing the manipulation). But it didn't work that way. Both transaction were rolled back. Can anyone give me any pointers to why this happened? Its kinda conflicting what I thought I knew how the system works. Does currentTransactionStatus() include ALL transactions associated with the current session?

Relevant Code snippet (shortend for clarity)

@Override
@Transactional
@PreAuthorize(Rollen.INFOVN)
public InfoVNAntwort infoVNAnfrage(final InfoVNAnfrage infoVNAnfrage) {

    // extract data from request
    final InfoVNDatenhalter datenhalter = (InfoVNDatenhalter) this.getController().erzeugeNeuenDatenhalter(ProzessNamen.INFOVN);
    datenhalter.setAnfrageFin(StringUtils.trimToNull(infoVNAnfrage.getFIN()));
    datenhalter.setAnfrageZB2(StringUtils.trimToNull(infoVNAnfrage.getZB2()));
    final String username = this.getCurrentUserName();
    datenhalter.setBenutzerkennung(username);
    datenhalter.setErgaenzungstext(infoVNAnfrage.getErgaenzungstext());
    datenhalter.setAnfragegrund(infoVNAnfrage.getAnfrageanlass());

    // actual fetch of database data
    final DialogAntwort da = (DialogAntwort) this.getController().verarbeite(datenhalter);

    // convert to ws response
    final InfoVNAntwort vnAntwort = this.getMapper().map(da, InfoVNAntwort.class);

    // log who did what
    this.erstelleBewirt(vnAntwort, infoVNAnfrage, new DateTime(), username);

    // roll back outer transaction
    TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

    return vnAntwort;
}

@Transactional(propagation = Propagation.REQUIRES_NEW)
private void erstelleBewirt(final InfoVNAntwort vnAntwort, final InfoVNAnfrage infoVNAnfrage, final DateTime zeitpunktAuskunft, final String username) {
    final InfoVNBewirt eintrag = new InfoVNBewirt();
    eintrag.setZeitpunktErteilungAuskunft(zeitpunktAuskunft);
    eintrag.setSteuerelement(STEUERELEMENT_INFOVN);
    eintrag.setAnfrageAnlass(infoVNAnfrage.getAnfrageanlass());
    this.completeEintrag(username, eintrag);
    this.logdatenPersister.persistiereLogdaten(eintrag);
}

Database-Connection:

    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
        <property name="dataSource" ref="dataSource"></property>
        <property name="packagesToScan">
            <list>
                <value>de.mm.vwn</value>
                <value>de.mm.cfo.allgemein.kenauthent</value>
                <value>de.mm.cfo.infovn.logdaten</value>
            </list>
        </property>
        <property name="hibernateProperties" ref="hibernateProperties"></property>
    </bean>

    <bean id="hibernateProperties"
        class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="properties">
            <props>
                <prop key="hibernate.dialect">${hibernate.dialect}</prop>
                <prop key="hibernate.hbm2ddl.auto">none</prop>
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">false</prop>
                <prop key="hibernate.cache.use_second_level_cache">true</prop>
<prop key="hibernate.cache.use_query_cache">true</prop>
<prop key="hibernate.cache.region.factory_class">net.sf.ehcache.hibernate.EhCacheRegionFactory</prop>
<prop key="hibernate.jdbc.use_scrollable_resultset">true</prop>
<prop key="hibernate.jdbc.batch_size">25</prop>
            </props>
        </property>
    </bean>

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

    <tx:annotation-driven />

Upvotes: 2

Views: 1304

Answers (2)

JB Nizet
JB Nizet

Reputation: 691635

OK. The problem is the one I thought.

Here's how Spring makes beans transactional: when you get a transactional bean from the Spring bean factory, or thanks to dependency injection, Spring doesn't give you an instance of your bean class. It gives you a proxy which has the same interface as your bean class, and delegates all the method calls to an instance of your bean class, except it starts a transaction (if needed) before invoking the method, and rollbacks/commits the transaction (if needed) after the method has returned:

Client ----> transactional proxy ----> bean.infoVNAnfrage()

If, from your bean class instance (InfoVNAntwort), you call another method of the same bean, the method invocation doesn't go through the proxy:

Client ----> transactional proxy ----> bean.infoVNAnfrage() ----> bean.erstelleBewirt()

So Spring has no way to start a new transaction for erstelleBewirt().

The easier way is to put the erstelleBewirt() method into another transactional Spring bean, and to inject this other Spring bean into your current bean:

Client ----> transactional proxy ----> bean.infoVNAnfrage() ----> transactional proxy ----> otherBean.erstelleBewirt()

Upvotes: 2

Nandkumar Tekale
Nandkumar Tekale

Reputation: 16158

I think you just need to use single @Transactional on method(You can also use on class level). Like this example :

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateFoo(Foo foo) {//transaction1
    // do something
    updateFoo1(); 
}

public void updateFoo1() {//transaction 2
    // do something
}

Upvotes: 0

Related Questions