强制回滚事务会导致嵌套事务还回滚吗?

时间:2012-07-06 10:10:31

标签: java spring hibernate transactions

情况 - 故事时间:
我“继承”了一个程序,一个用于访问数据库的相当简单的Web服务。这个程序有一个缺陷:它试图更新一个没有更新授权的表。该程序只有权更新数据库队列(Oracle),以保存访问过的内容的信息。这是不受欢迎的行为,现在我修复了它。注意:这与这个问题本身无关,只是导致我提出这个问题的原因。

该程序使用Spring + Hibernate来管理和访问数据和事务。

因为程序需求量很大并且错误被认为是无法容忍的,所以我很快就想添加一个修补程序,只是为了强制对每个事务进行回滚,直到找到实际操作数据的软件部分它不应该操纵。

该软件使用2 @Transactional个注释。一个是整个过程,另一个是编写日志数据的部分。第二个与Requires_New传播设置一起使用。据我所知,第二个将始终创建一个新的Transaction并在其跨度(在这种情况下:一个方法)结束时刷新它。

然后,在嵌套事务已经结束后,我添加了一个直接的Rollback-Statement TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();来回滚外部事务(因此撤消操作)。但它并没有这样的方式。两笔交易均已回滚。任何人都可以给我任何指示,为什么会发生这种情况?它有点矛盾,我认为我知道系统如何工作。 currentTransactionStatus()是否包含与当前会话关联的所有事务?

相关代码段(为清晰起见,简称

@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);
}

数据库-连接:

    <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 />

2 个答案:

答案 0 :(得分:2)

行。问题是我想到的那个问题。

以下是Spring如何使bean事务化:当你从Spring bean工厂获得一个事务bean时,或者由于依赖注入,Spring不会给你一个bean类的实例。它为您提供了一个与bean类具有相同接口的代理,并将所有方法调用委托给bean类的实例,除了它在调用方法之前启动事务(如果需要),并回滚/提交事务(如果需要)方法返回后:

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

如果从bean类实例(InfoVNAntwort)调用同一个bean的另一个方法,则方法调用不会通过代理:

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

因此Spring无法为erstelleBewirt()开始新的交易。

更简单的方法是将erstelleBewirt()方法放入另一个事务Spring bean中,并将另一个Spring bean注入到当前的bean中:

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

答案 1 :(得分:0)

我认为你只需要在方法上使用单@Transactional(你也可以在类级别使用)。像这个例子:

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

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