2013-06-04 121 views
2

在試圖解決以下問題的同時,在過去幾天裏,白髮的數量急劇增加。我在使用簡單的Spring 3.2事件機制的自定義事件偵聽器中使用Spring Data JPA存儲庫。我遇到的問題是,如果ListenerA創建實體並調用assetRepository.save(entity)assetRepository.saveAndFlash(entity),則後續調用將從另一個偵聽器檢索到該實體失敗。原因似乎是ListenerB無法在數據庫中找到原始實體,它似乎仍在Hibernate的緩存中。 ListenerB鎖定實體的觸發器是由於線程池中的可運行任務執行而觸發的事件。 這裏是我的配置:春季數據JPA存儲庫,春季交易和事件發生

<bean id="entityManagerFactory" 
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> 
    <property name="dataSource" ref="dataSource" /> 
    <property name="persistenceUnitName" value="spring-jpa" /> 
    <property name="jpaVendorAdapter"> 
     <bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter"> 
      <property name="generateDdl" value="false" /> 
      <property name="database" value="#{appProps.database}" /> 
     </bean> 
    </property> 
    <property name="jpaProperties"> 
     <props> 
      <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop> 
      <prop key="hibernate.hbm2ddl.auto">#{appProps['hibernate.hbm2ddl.auto']}</prop> 
      <prop key="hibernate.show_sql">#{appProps['hibernate.show_sql']}</prop> 
      <prop key="hibernate.format_sql">#{appProps['hibernate.format_sql']}</prop> 
      <prop key="hibernate.search.default.directory_provider">org.hibernate.search.store.impl.FSDirectoryProvider</prop> 
      <prop key="hibernate.search.default.indexBase">#{appProps.indexLocation}</prop> 
      <prop key="hibernate.search.lucene_version">#{appProps['hibernate.search.lucene_version']}</prop> 
     </props> 
    </property> 
</bean> 

<tx:annotation-driven transaction-manager="transactionManager" /> 

<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager"> 
    <property name="entityManagerFactory" ref="entityManagerFactory" /> 
    <property name="jpaDialect"> 
     <bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" /> 
    </property> 
</bean> 

我省略了dataSource配置,它的ComboPooledDataSource一個實例,它定義連接Oracle數據庫。作爲一個方面說明,使用了組件掃描,並且該項目是Spring MVC。 現在是Java類。

ListenerA

@Sevice 
public class ListenerA implements ApplicationListener<FileUploadedEvent> { 

    @Autowired 
    private AssetRepository assetRepository; 

    @Autowired 
    private ExecutorService executor; // Triggers runnable task on a Job in Spring's TaskExecutor 

    @Override 
    @Transactional 
    public void onApplicationEvent(FileUploadedEvent event) { 

    Asset target = event.getTarget(); 
    Job job = new Job(target); 
    assetRepository.save(job); 

    executor.execute(job); 
} 

ListenerB

@Sevice 
public class ListenerB implements ApplicationListener<JobStartedEvent> { 

    @Autowired 
    private AssetRepository assetRepository; 


    @Override 
    @Transactional 
    public void onApplicationEvent(JobStartedEvent event) { 

    String id = event.getJobId(); 
    Job job = assetRepository.findOne(id); // at this point we can not find the job, returns null 
    job.setStartTime(new DateTime()); 
    job.setStatus(Status.PROCESSING); 

    assetRepository.save(job); 
} 

JobStartedEvent從運行的任務中TaskExecutor解僱。 我在這裏做錯了什麼?我試圖使用自定義事件發佈者,這是事務意識,但似乎並沒有解決問題。我也嘗試連接適當的服務而不是數據存儲庫,並從聽衆中刪除@Transactional註釋,這些註釋也失敗了。歡迎任何有關如何解決問題的合理建議。

+0

你確定事務註解在onapplicationevent方法上工作嗎?如果您在服務bean內部創建作業並將事務註釋移到那裏,會發生什麼? –

+0

我傾向於認爲這個註釋正在工作,因爲Job嵌入了執行過程中需要的延遲加載收集。關於第二個問題,我設法通過用服務替換Spring Data存儲庫來解決這個問題。然而,爲了這個工作,我不得不用'@Transactional(propagation = Propagation.REQUIRES_NEW)'註釋初始化方法(其中Job對象首先創建並保存)。如果這是解決此問題的正確方法,我不是100%確定的。爲什麼我只能在聽衆中使用存儲庫? – pilot

+0

這可能不是原因,但無論如何 - 您似乎在退出事務性塊並提交事務之前調用executor.execute(job)。您不能100%保證在執行onApplicationEvent之前事務將被提交。 –

回答

2

由於來自@Kresimir Nesek的提示,我設法解決了這個問題。所以解決方案是用適當的服務來替換Spring Data存儲庫。 這裏是修改的類。

聽者

@Sevice 
public class ListenerA implements ApplicationListener<FileUploadedEvent> { 

    @Autowired 
    private JobService service; 

    @Autowired 
    private ExecutorService executor; // Triggers runnable task on a Job in Spring's TaskExecutor 

    @Override 
    public void onApplicationEvent(FileUploadedEvent event) { 

    Job job = service.initJobForExecution(event.getTarget()); 

    executor.execute(job); 
    } 
} 

JobService方法initJobForExecution(Asset target)必須與@Transactional(propagation=Propagation.REQUIRES_NEW)進行註釋的一切工作。

監聽乙

@Sevice 
public class ListenerB implements ApplicationListener<JobStartedEvent> { 

@Autowired 
private JobService service; 


@Override 
public void onApplicationEvent(JobStartedEvent event) { 
    service.updateStatus(event.getJobId(), Status.PROCESSING); 
} 
} 
1

雖然這雖是略顯老套,我跑在這個同樣的問題,但現在春4.1.1.RELEASE春數據JPA 1.7.0休眠4.3.5.Final

我的方案發生在測試過程中,一些測試失敗。在測試期間,我們的問題是由H2在單連接模式下引起的,廣播異步事件和事件事務性。

解決方案

  1. 第一個問題是,由於事務超時,並通過添加MVCC=true至H2 URL字符串解決。參見:https://stackoverflow.com/a/6357183/941187

  2. 異步事件在測試過程中引發問題,因爲它們在不同的線程上執行。在事件配置中,使用了任務執行程序和線程池。要修復,只需使用SyncTaskExecutor作爲任務執行程序提供重寫的配置Bean。這將導致所有事件同步發生。

  3. 事件的事務性很棘手。在我們的框架中,事件從交易中獲得廣播(@Transactional)。事件然後在事務上下文之外的另一個線程上處理。這引入了一種競爭條件,因爲處理程序通常依賴於事務中的對象實施。我們沒有注意到我們的Windows開發機器上的問題,但是在Linux上部署到生產環境時變得很明顯。該解決方案使用TransactionSynchronizationManager.registerSynchronization(),執行TransactionSynchronization.afterCommit()以在提交後廣播該事件。有關更多信息和示例,請參見http://www.javacodegeeks.com/2013/04/synchronizing-transactions-with-asynchronous-events-in-spring.html

  4. 與#3相關,我們必須爲某些事件處理程序調用的某些服務方法添加@Transactional(propagation = REQUIRES_NEW)

希望這有助於一些遲來的人。