2014-07-23 55 views
8

我正在構建一個使用Spring Data和Hibernate的簡單Tomcat webapp。有一個端點可以完成很多工作,所以我想將工作轉移到後臺線程,以便在完成工作時Web請求不會掛起10分鐘以上。所以,我在一個組件scan'd包了一個新服務:如何在使用Spring Data和Hibernate時正確地創建後臺線程?

@Service 
public class BackgroundJobService { 
    @Autowired 
    private ThreadPoolTaskExecutor threadPoolTaskExecutor; 

    public void startJob(Runnable runnable) { 
     threadPoolTaskExecutor.execute(runnable); 
    } 
} 

然後在春天配置ThreadPoolTaskExecutor

<bean id="threadPoolTaskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 
    <property name="corePoolSize" value="5" /> 
    <property name="maxPoolSize" value="10" /> 
    <property name="queueCapacity" value="25" /> 
</bean> 

這是所有偉大的工作。但是,問題來自Hibernate。在我的可運行內部,查詢只有一半工作。我可以這樣做:

MyObject myObject = myObjectRepository.findOne() 
myObject.setSomething("something"); 
myObjectRepository.save(myObject); 

但是,如果我有懶加載領域,它失敗:

MyObject myObject = myObjectRepository.findOne() 
List<Lazy> lazies = myObject.getLazies(); 
for(Lazy lazy : lazies) { // Exception 
    ... 
} 

我收到以下錯誤:

org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.stackoverflow.MyObject.lazies, could not initialize proxy - no Session 

所以它看起來像我(休眠新手),新線程在這些自制線程上沒有會話,但Spring Data會自動爲HTTP請求線程創建新會話。

  • 有沒有辦法在會話內手動啓動一個新的會話?
  • 還是一種告訴線程池爲我做的方法?
  • 做這類工作的標準做法是什麼?

我已經能夠解決它一點點從一個@Transactional方法裏面做的一切,但我很快意識到這不是一個很好的解決方案,因爲這並不讓我使用的工作方法對Web請求來說很好。

謝謝。

回答

0

會發生什麼情況,可能是您在DAO代碼片段上有事務處理,Spring將在事務關閉時關閉會話。

你應該把你所有的業務邏輯融入單一交易。

您可以將SessionFactory注入您的代碼並使用SessionFactory.openSession()方法。
問題是,你將不得不管理你的交易。

+0

部分問題是,我希望能夠在作業運行時更新作業的狀態。所以如果我把它全部包裝在單個事務中,那麼在最外層提交提交之前,狀態不會更新。除非我在這裏錯過了一些東西......這是完全可能的:) – Joel

+0

你可以展示你的Runnable實現類,也許你的ObjectRepository? 我也很好奇你如何以及在哪裏打算髮布狀態更新。 基於http的網絡應用程序並不是那麼明顯。 – alobodzk

+0

專門用於更新進程狀態:使用嵌套事務。確保你的JPA提供者支持他們。 – Virmundi

19

隨着春天,你不需要你自己的執行者。一個簡單的註釋@Async將爲您完成這項工作。只需在您的服務中註釋您的heavyMethod,並返回void或Future對象,您將獲得後臺線程。我會避免在控制器級別使用異步註釋,因爲這會在請求池執行器中創建異步線程,並且可能會用盡'請求接受者'。

您的懶惰異常的問題來自您從沒有會話的新線程懷疑。爲了避免這個問題,你的異步方法應該處理完整的工作。不要提供先前加載的實體作爲參數。該服務可以使用EntityManager,也可以是事務性的。

我自己不合並@Async@Transactional所以我可以以任何方式運行服務。我只是在服務周圍創建異步包裝,並根據需要使用此包裝。 (這簡化了例如測試)

@Service 
public class AsyncService { 

    @Autowired 
    private Service service; 

    @Async 
    public void doAsync(int entityId) { 
     service.doHeavy(entityId); 
    } 
} 

@Service 
public class Service { 

    @PersistenceContext 
    private EntityManager em; 

    @Transactional 
    public void doHeavy(int entityId) { 
     // some long running work 
    } 
} 
+0

這項技術對我的傳統Spring Roo 1.3.x應用程序來說是完美的。我不得不加強傳統應用程序,它像一個魅力。謝謝!! – sunitkatkar

-1

方法1:JPA實體管理器

在後臺線程:注入實體管理器或Spring上下文獲得它或把它作爲參考:

@PersistenceContext 
private EntityManager entityManager;  

然後創建一個新的實體管理器,以避免使用共享的一個:

EntityManager em = entityManager.getEntityManagerFactory().createEntityManager(); 

現在你可以開始交易,並使用Spring DAO,倉庫,JPA等

private void save(EntityManager em) { 

    try 
    {   
     em.getTransaction().begin();     

     <your database changes> 

     em.getTransaction().commit();       
    } 
    catch(Throwable th) { 
     em.getTransaction().rollback(); 
     throw th; 
    }   
} 

方法2:JdbcTemplate的

如果你需要低級別的改變或你的任務是很簡單的,在你的方法

@Autowired 
private JdbcTemplate jdbcTemplate; 

然後某處:你可以用JDBC和查詢做手工

jdbcTemplate.update("update task set `status`=? where id = ?", task.getStatus(), task.getId()); 

附註:我會建議遠離@Transactional,除非你使用JTA或依賴JpaTransactionManager。

+0

這是不正確的! @Transactional不需要JTA。事務管理器也可以是使用EntityManager處理事務的org.springframework.orm.jpa.JpaTransactionManager! –

+0

感謝您的反饋。我現在提出了關於@Transactional的建議。它不影響關於如何在後臺線程中處理問題的工作建議的內容和正確性。 – alexzender

+0

我仍然不同意,因爲處理您自己的交易可能很麻煩。在你的例子中,當調用em.getTransaction()。rollback();交易可以是非活動的。你必須檢查這個! –

相關問題