博客 / 詳情

返回

spring jpa關於線程池異步執行導致detached entity passed to persist問題排查和解決

我這邊有個批量插入用户OpenUser和應用OpenApp關聯關係數據的操作,由於耗時較長時間,所以準備用線程池異步執行操作,然而卻遇到了一個jpa的detached entity passed to persist問題,我這邊的操作是批量保存一個OpenAppUser關聯關係表,所以需要先獲得對應OpenUser和OpenApp的引用,再設置到關聯對象OpenAppUser裏,然後在保存,我這邊是先通過userRepository.findById(userId)獲取到OpenUser,然後openAppUser.setOpenUser(openUser),在執行appUserRepository.save(openAppUser);時發生瞭如標題上的錯誤,説是OpenUser對象處於遊離態,無法保存。

經過排查,我這邊是因為OpenAppUser類裏設置了@ManyToOne(cascade = CascadeType.ALL)級聯OpenUser,所以在保存OpenAppUser的時候會級聯操作OpenUser,本來在沒有開線程異步的情況下,因為OpenUser之前通過findById查出來了,所以在jpa的PersistenceContext裏是有該OpenUser的脱管對象的,這時候就不會報錯,而在線程異步的情況下context裏確沒有該脱管對象了
(這裏説明一下,為啥不開線程有,開了線程沒有?)因為spring-boot默認jpa.open-in-view=true,會使用ThreadLocal在當前線程裏保存EntityManager上下文信息,所以在整個controller裏都是使用的同一個context

PersistenceContext持久性上下文有兩種類型:

  • 事務範圍的持久性上下文;當我們在事務中執行任何操作時,EntityManager 會檢查持久性上下文。 如果存在,則將使用它。否則,它將創建一個持久性上下文
  • 擴展範圍的持久性上下文;擴展持久性上下文可以跨越多個事務。我們可以在沒有事務的情況下持久化實體,但不能在沒有事務的情況下刷新它。

在@PersistenceContext註解裏type可以指定範圍:PersistenceContextType.TRANSACTION;PersistenceContextType.EXTENDED

而當我們用線程池異步的時候,拿不到之前的EntityManager的配置信息,而spring jpa repository默認的方法上都會自帶一個事務,所以在執行完userRepository.findById(userId)獲取到OpenUser之後,會commit,而commit操作會clear掉EntityManager裏保存的脱管對象OpenUser,等到appUserRepository.save(openAppUser);保存的時候,由於引用的OpenUser已經沒有在PersistenceContext上下文裏了,不是脱管對象了(具體可以看EntityState entityState = getEntityState( entity, entityName, entityEntry, source );裏面的實現,有幾種判斷條件,是不是脱管對象,有沒有id、version等等屬性),就會報detached entity passed to persist這個異常

所以根據實際情況,我們只要參考open-in-view=true產生對應的OpenEntityManagerInViewInterceptor攔截器改造一下自己線程裏的PersistenceContext上下文生效範圍,就可以解決該異常了

user avatar docker_app 頭像
1 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.