2

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

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

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

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

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

2 回答 2

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()
于 2012-07-06T13:57:18.413 回答
0

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

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

public void updateFoo1() {//transaction 2
    // do something
}
于 2012-07-06T10:36:23.270 回答