JPA(Hibernate)實體狀態
一句話總結:通過JPA(Hibernate)對實體物件進行增刪改查時,JPA(Hibernate)要維護即儲存物件在Session即Hibernate一級快取中,同時還要維護其狀態,具體狀態變化如下圖所示:
通常我們無需關心JPA(Hibernate)的實體狀態,但是碰到一些問題時需要對實體狀態有所瞭解。
問題一:Cannot remove detached entity異常
通常我們會通過Spring配置自動事務,此時若在北向呼叫ServiceA查詢出一個物件,這裡查詢也會有事務提交,那麼這個物件其實處於detached狀態,北向再呼叫ServiceB傳遞此物件進行刪除,就會報錯提示cannot remove detached entity異常。還有一個場景是批量提交場景,網上的示例通常在批量提交後會呼叫em.clear來釋放記憶體,此時該物件也處於detached狀態,再remove同樣會報錯。
northbound method() { Object obj = service.findObject(id); service.removeObject(obj); } service findObject method() { // Spring事務,自動提交commit return object; } service removeObject method(Object obj) { // 這裡會報錯 JpaEntityManager.remove(obj); }
問題二:瞭解實體狀態後,我們知道事務自動提交後再操作物件會有detach異常,那麼設計架構時就要確保每次REST請求時使用不同的Hibernate Session,若併發多次請求使用同一Session裡快取的物件,就得手動去維護物件的狀態才能防止出現detach異常。
通常我們還會使用Spring IOC,注入單例物件,這會讓我們覺得Hibernate Session是同一個,如下:
class Northbound { @Autowired Service service; } class Service { @PersistenceContext EntityManager em; }
但其實Spring已經幫我們隱含處理了,這裡有點繞,Northbound注入Service時是單例的,每次呼叫的是同一個service物件。但Service注入EntityManager時,卻會根據每個執行緒即每次REST請求注入新的EntityManager(其下封裝Hibernate Session)。Spring會針對PersistenceContext註解做特殊處理,將EntityManager(Session)與執行緒繫結,儲存在ThreadLocal中。在OSGI環境中,還會使用OSGI Coordinate機制。所以用斷點除錯去檢視,每次請求的EntityManager物件都是不同的。
引用:
https://docs.oracle.com/cd/E16439_01/doc.1013/e13981/undejbs003.htm
https://openjpa.apache.org/builds/1.2.3/apache-openjpa/docs/jpa_overview_em_lifecycle.html
https://stackoverflow.com/questions/42074270/should-there-be-an-entitymanager-per-thread-in-spring-hibernate