【问题标题】:Forced rollback of Transaction causes Nested Transactions also to roll back?强制回滚事务会导致嵌套事务也回滚?
【发布时间】:2012-07-06 10:10:31
【问题描述】:

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

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

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

软件使用了2个@Transactional注解。一个在整个过程中,另一个在写入日志数据的部分。第二个与Requires_New 传播设置一起使用。据我了解,第二个事务总是会创建一个新事务并在其跨度(在本例中:一种方法)结束时刷新它。

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

相关代码 sn-p(为清楚起见缩写)

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

【问题讨论】:

  • 不,这不正常,除非您不通过事务代理调用具有 REQUIRES_NEW 的方法。向我们展示您的代码。

标签: java spring hibernate transactions


【解决方案1】:

好的。问题是我想的那个。

以下是 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()

【讨论】:

    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-07-21
      • 1970-01-01
      • 2011-04-16
      • 1970-01-01
      • 1970-01-01
      • 2020-04-11
      • 2021-05-14
      相关资源
      最近更新 更多