2012-07-06 111 views
2

情況 - 故事時間:
我「繼承了」一個程序,一個用於訪問數據庫的相當簡單的web服務。這個程序有一個缺點:它試圖更新一個沒有更新授權的表。該程序只有更新數據庫隊列(Oracle)的權利,以保存訪問內容的信息。這是不受歡迎的行爲,現在我已經修復了它。注意:這與這個問題本身無關,只是導致我這個問題的原因。強制回滾事務導致嵌套事務也回滾?

該程序使用Spring + Hibernate來管理和訪問數據和事務。因爲程序需求量很大,錯誤被認爲是不可容忍的,所以我有一個快速的想法來添加一個修補程序,只是爲了強制每個事務回滾,直到我發現軟件的一部分實際上處理數據時它應該不操縱。

該軟件使用2 @Transactional註釋。一個是整個過程,另一個是寫日誌數據。第二個與Requires_New傳播設置一起使用。據我瞭解,第二個總是會創建一個新的Transaction,並在其跨度(在這種情況下:一個方法)結束時刷新它。

然後我在嵌套事務結束後添加了一個直接回滾語句TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();來回滾外部事務(爲了撤銷操作)。但它沒有那樣工作。這兩個事務都回滾了。任何人都可以給我指出爲什麼發生這種情況?它有點相互衝突,我認爲我知道系統是如何工作的。 currentTransactionStatus()是否包含與當前會話關聯的所有交易?

相關代碼段(shortend爲清楚起見)

@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

不,這是不正常的,除非你不經歷事務代理調用具有REQUIRES_NEW的方法。告訴我們你的代碼。 – 2012-07-06 10:17:14

回答

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() 
0

我想你只需要在方法採用單@Transactional(你也可以在類級別使用) 。像這個例子:

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

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