2016-04-06 61 views
8

我的應用程序加載應該處理的實體列表。這發生在使用REQUIRES_NEW爲傳播層面使用調度我應該將管理實體傳遞給需要新事務的方法嗎?

@Component 
class TaskScheduler { 

    @Autowired 
    private TaskRepository taskRepository; 

    @Autowired 
    private HandlingService handlingService; 

    @Scheduled(fixedRate = 15000) 
    @Transactional 
    public void triggerTransactionStatusChangeHandling() { 
     taskRepository.findByStatus(Status.OPEN).stream() 
           .forEach(handlingService::handle); 
    } 
} 

在我HandlingService處理每個任務issolation類。

@Component 
class HandlingService { 

    @Transactional(propagation = Propagation.REQUIRES_NEW) 
    public void handle(Task task) { 
     try { 
      processTask(task); // here the actual processing would take place 
      task.setStatus(Status.PROCCESED); 
     } catch (RuntimeException e) { 
      task.setStatus(Status.ERROR); 
     } 
    } 
} 

代碼工作只是因爲我在TaskScheduler類啓動父事務。如果我刪除@Transactional註釋,則不再管理這些實體,並且任務實體的更新不會傳播到db。我不覺得自然而然地使該預定方法成爲事務性的。

從我看到我有兩個選擇:今天

1.保持代碼,因爲它是。

  • 也許這只是我,這是一個正確的方法。
  • 此變體對數據庫的訪問次數最少。

2.從計劃中刪除@Transactional註解,通過任務的ID,並重新加載在HandlingService任務實體。

@Component 
class HandlingService { 

    @Autowired 
    private TaskRepository taskRepository; 

    @Transactional(propagation = Propagation.REQUIRES_NEW) 
    public void handle(Long taskId) { 
     Task task = taskRepository.findOne(taskId); 
     try { 
      processTask(task); // here the actual processing would take place 
      task.setStatus(Status.PROCCESED); 
     } catch (RuntimeException e) { 
      task.setStatus(Status.ERROR); 
     } 
    } 
} 
  • 擁有更多的去使用@Async

能否請您提供您的意見上這是正確的道路數據庫(一個額外的查詢/元)

  • 可以執行解決這類問題,或許用另一種我不知道的方法?

  • 回答

    9

    如果您的意圖是在單獨的事務中處理每個任務,那麼您的第一種方法實際上不起作用,因爲在調度程序事務結束時所有事務都已提交。

    原因是在嵌套事務中Task實例基本上是分離實體(Session在嵌套事務中啓動時並不知道這些實例)。在調度程序事務結束時,Hibernate對受管實例執行髒檢查並將更改與數據庫同步。

    這種方法也是非常危險的,因爲如果您嘗試訪問嵌套事務中的Task實例上的未初始化代理時可能會出現問題。如果在嵌套事務中更改Task對象圖,則可能會遇到問題,方法是向嵌套事務中加載其他一些實體實例(因爲當控件返回到調度程序事務時,現在將分離該實例)。

    另一方面,您的第二種方法是正確和直接的,並有助於避免上述所有的陷阱。只有,我會閱讀ID並提交事務(在處理任務時不需要保持它處於暫停狀態)。實現它的最簡單方法是從調度程序中刪除Transactional註釋,並使存儲庫方法成爲事務性(如果它不是事務性的)。

    如果(且只有)第二種方法的性能出現問題,正如您已經提到的那樣,您可以使用異步處理或甚至在一定程度上並行處理。另外,你可能想看看extended sessions(對話),也許你會發現它適合你的用例。

    +0

    在此示例中,嵌套事務的會話實體緩存是否與外部事務的會話同步?例如,如果「任務」實體在嵌套事務內部發生更改,那麼該更改是否也適用於出事務會話? – froi

    +0

    跟着我的問題,因爲外部事務的會話被刷新,這是否意味着任務更改將被視爲「陳舊」更改? – froi

    1

    假設processTask(task);HandlingService類(同handle(task)法)的方法,然後除去@TransactionalHandlingService因爲Spring的動態代理的自然行爲將無法正常工作。

    spring.io forum報價:

    春加載您TestService的其與代理包裹。如果您在TestService之外調用TestService的方法,代理將被調用,而您的事務將被正確管理。但是,如果您在同一對象的方法中調用事務方法,則不會調用代理,而是直接調用代理的目標,並且不會執行纏繞服務的代碼來管理事務。

    This is one of SO thread關於這個話題,下面是關於這一些文章:

    1. http://tutorials.jenkov.com/java-reflection/dynamic-proxies.html
    2. http://tutorials.jenkov.com/java-persistence/advanced-connection-and-transaction-demarcation-and-propagation.html
    3. http://blog.jhades.org/how-does-spring-transactional-really-work/

    如果你真的不喜歡加入@Transaction註解在你的@Scheduled見面HOD,你可以從EntityManager的獲得交易和程序化管理的事務,例如:

    UserTransaction tx = entityManager.getTransaction(); 
    try { 
        processTask(task); 
        task.setStatus(Status.PROCCESED); 
        tx.commit(); 
    } catch (Exception e) { 
        tx.rollback(); 
    } 
    

    但我懷疑你會採取這種方式(當然,我不會)。最後,

    能否請您提供您的意見上是解決此類問題的

    有在這種情況下,沒有正確的方式的正確途徑。我個人的觀點是,註釋(例如,@Transactional)只是標記,並且您需要註釋處理器(在本例中爲spring)才能使@Transactional正常工作。如果沒有處理器,註釋將毫無影響。

    我會更擔心,例如,爲什麼我processTask(task)task.setStatus(Status.PROCESSED);processTask(task)之外,如果它看起來像做同樣的事情,等

    HTH。

    +0

    即使'processTask'方法是'HandlingService'中的私有方法,所有更新都應該傳播到db,因爲當調用'handle'方法(雖然是Spring代理)時會打開一個新事務。 在第二個變體中,我建議從調度程序中刪除'@ Transctional'並將id傳遞給處理服務(這將爲每個處理後的任務打開事務) – mvlupan

    2

    當前代碼處理嵌套事務中的任務,但更新外部事務中任務的狀態(因爲Task對象由外部事務管理)。因爲這些是不同的事務,所以有可能一個成功,另一個失敗,使數據庫處於不一致的狀態。特別是,通過此代碼,如果處理其他任務引發異常,或者在處理完所有任務之前重新啓動服務器,則完成的任務保持打開狀態。

    如您的示例所示,將託管實體傳遞給另一個事務使得它不清楚哪個事務應該更新這些實體,因此最好避免。相反,您應該傳遞ID(或分離的實體),並避免不必要的事務嵌套。

    相關問題