現象
最近解決了一個困惑幾天的bug,數據庫里的某一些記錄莫名其妙的被刷新了,排查過代碼跟應用日志,可以確定不是代碼執行的更新。直到今天看到了一條日志,在事務提交時報錯“Column 'user_name' cannot be null”,在出錯的事務中,針對這一個表只會執行query不會執行update,而這個報錯信息是只有insert或者update時才有可能出現,這就意味着事務中自動在這個表執行了的insert或者update語句。
產生的原因
JPA通過EntityManager對數據庫實體類進行管理,而實體對象的狀態有new/managed/removed/detached四種狀態,如下圖所示
-
瞬時狀態(new/transient):
當一個實體對象最初被創建時,它的狀態是New或Transient。在這種狀態下,對象還沒有與EntityManager關聯,並且不存在數據庫中。 -
持久/托管狀態(managed):
當實體對象通過EntityManager的persist()方法持久化到數據庫時,它就變成了Managed或Persistent。如果我們更改持久狀態對象的值,則在提交事務時自動與數據庫同步。由EntityManager從數據庫檢索的實體對象也處於Managed狀態。 -
游離狀態(detached):
狀態Detached表示已經與EntityManager斷開連接的實體對象。對象標識符應該在數據庫表中,對象不與持久化上下文相關聯。當關閉hibernate會話時,實例將失去與持久性管理器的關聯。我們把這些物體稱為分離的。表明它們的狀態不再保證與數據庫同步。游離的對象可以在以后的時間點重新附加到新會話,使其以及所有修改再次持久化 -
移除狀態(removed):
通過在活動事務中使用entitymanager的remove(),也可以將持久狀態實體對象標記為要刪除。然后,實體對象將其狀態從Managed更改為Removed,並在提交期間從數據庫中物理刪除。
當數據從數據庫查出來時,該數據對象處於managed狀態中,管理實體對象在活動事務中被修改,該更改由所屬的EntityManager檢測到,並在事務提交時將更新到數據庫
一旦實體對象從數據庫中檢索出來,它可以在內存中修改將會反映到數據庫當中,如下偽代碼
EntityManager em;
Employee employee = em.find(Employee.class, 1);
em.getTransaction().begin();
employee.setNickname("Ram");
em.getTransaction().commit();
當事務提交時,實體對象在數據庫中nickName將會更新為Ram,而代碼中並沒有調用persist()。
解決方法
避免直接修改處於managed狀態的數據對象,可以復制對象,使用副本