情况 - 故事时间:
我“继承”了一个程序,一个用于访问数据库的相当简单的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 />
答案 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
}