Hibernate實踐—持久態對象的變化與入庫機制
持久化后修改屬性,會發生什么
@Test
public void demo3(){
UserInfo userInfo = new UserInfo();
userInfo.user_name = "demo123";
userInfo.user_sex = 1;
userInfo.user_age = 18;
UserPassword userPassword = new UserPassword();
userPassword.password = "demo-password";
userInfo.userPassword = userPassword;
userPassword.UserPassword_userInfo = userInfo;
BaseEntityUtils.save(userInfo);
System.out.println(userInfo.user_name);
userInfo.user_name = "demo1234";
userPassword.password = "demo1234-password";
UserInfo new1 = BaseEntityUtils.findById(userInfo.id,UserInfo.class);
System.out.println(new1.user_name);
}
在提供的代碼中,在執行 BaseEntityUtils.saveAndFlush(userInfo) 之后,userInfo 和 userPassword 對象處于持久態(Persistent State)。
userInfo 對象的狀態
- 由于使用了 BaseEntityUtils.saveAndFlush(userInfo) 方法,userInfo 對象被保存到數據庫,并且通過 flush 操作將其狀態同步到數據庫中。
- 因此,userInfo 對象現在處于持久態,它受到 Hibernate Session 的管理。
userPassword 對象的狀態
- userPassword 對象與 userInfo 之間存在一對一的關聯關系,由于級聯關系的存在,userPassword 對象也被保存到數據庫中。
- userPassword 對象的狀態也是持久態,它同樣受到 Hibernate Session 的管理。
在持久態下,任何對 userInfo 和 userPassword 對象的屬性的修改都將被監測到,并在適當的時候同步到數據庫中。在測試方法的最后,我們對 userPassword 的 password 屬性和 userInfo 的 user_name 屬性進行了修改。這些修改將在 Hibernate Session 中被跟蹤,但由于在測試方法中并沒有進行事務的提交或刷新,這些變化可能尚未同步到數據庫。如果我們想要確保變化及時更新到數據庫中,我們可以在測試方法的最后添加 BaseEntityUtils.flush() 或者使用 Spring 的 @Transactional 注解,以確保事務的正確提交和刷新。這樣,持久態下的對象變化將會及時同步到數據庫。
什么時候會刷新到數據庫
在 Hibernate 中,對于持久態的實體對象,屬性的修改會被監測到,并且會在以下情況下被同步到數據庫
事務提交時
當事務被提交時,Hibernate 會檢查事務中所有持久態對象的變化,并將這些變化同步到數據庫。這是最常見的觸發時機。
顯式調用 flush() 方法時
我們可以顯式調用 Hibernate Session 的 flush() 方法,強制將所有掛起的 SQL 語句發送到數據庫。這樣可以在事務未提交的情況下將變化同步到數據庫。
在查詢時自動執行 flush
Hibernate 在執行一些查詢操作(例如執行查詢語句之前)時,會自動執行 flush 操作,以確保最新的數據被加載。這種情況通常涉及到緩存和查詢的一致性。所以,當我們在持久態的對象上進行屬性的修改后,如果我們正在一個事務中,通常會在事務提交時或在顯式調用 flush() 方法時將變化同步到數據庫。在我們提供的測試方法中,如果沒有顯式調用 BaseEntityUtils.flush(),并且也沒有使用 Spring 的事務管理(例如,使用 @Transactional 注解),那么在測試方法執行完畢時,可能并沒有執行事務提交和 flush 操作,因此對 userInfo 和 userPassword 的屬性修改可能尚未同步到數據庫。在實際應用中,確保在需要同步變化時執行事務提交或顯式調用 flush 方法。
持久態的前提是什么
需要在同一個EntityManager中才能被自動更新到數據庫中。
持久態的前提是實體對象必須處于同一個持久化上下文(Persistence Context)中,而持久化上下文通常對應于一個 EntityManager。持久態實體對象是通過 EntityManager 進行管理和跟蹤的。具體來說,以下是實體對象變為持久態的前提條件
通過 EntityManager 進行持久化操作
持久態的實體對象通常是通過 EntityManager 的 persist、merge、find 等方法從數據庫中加載或保存得到的。在這些操作中,實體對象會被添加到 EntityManager 的持久化上下文中,從而成為持久態。
EntityManager entityManager = // 獲取 EntityManager 的方式
UserInfo userInfo = new UserInfo();
entityManager.persist(userInfo); // 將實體對象變為持久態
通過查詢操作獲取的實體對象
當通過查詢操作從數據庫中獲取實體對象時,這些對象也會成為持久態。例如,通過 find 方法獲取對象
EntityManager entityManager = // 獲取 EntityManager 的方式
UserInfo userInfo = entityManager.find(UserInfo.class, 1L); // 獲取持久態對象
在這種情況下,userInfo 對象會被添加到 EntityManager 的持久化上下文中。
級聯關系的影響
如果實體對象之間存在級聯關系,并且級聯操作的范圍包括持久化操作,那么在級聯操作中涉及到的對象也會變為持久態。
UserInfo userInfo = new UserInfo();
UserPassword userPassword = new UserPassword();
userInfo.setUserPassword(userPassword); // 設置級聯關系
在這個例子中,如果級聯關系包括 CascadeType.PERSIST,那么當 userInfo 被保存時,userPassword 也會成為持久態。總體而言,持久態的前提是實體對象必須由 EntityManager 進行管理,且這些實體對象需要處于同一個持久化上下文中。這確保了對實體對象的任何修改都能被 EntityManager 跟蹤并在適當的時候同步到數據庫。
問題分析
@Test
public void demo3(){
UserInfo userInfo = new UserInfo();
userInfo.user_name = "demo123";
userInfo.user_sex = 1;
userInfo.user_age = 18;
UserPassword userPassword = new UserPassword();
userPassword.password = "demo-password";
userInfo.userPassword = userPassword;
userPassword.UserPassword_userInfo = userInfo;
BaseEntityUtils.save(userInfo);
System.out.println(userInfo.user_name);
userInfo.user_name = "demo1234";
userPassword.password = "demo1234-password";
UserInfo new1 = BaseEntityUtils.findById(userInfo.id,UserInfo.class);
System.out.println(new1.user_name);
}
Alt text
在上面的代碼中,由于使用的是靜態方法去save,所以查詢方法和Save方法并不是使用的一個entityManager,所以此時這兩個對象的修改沒有被同步到數據庫中,兩次打印都是demo123。
在我們提供的代碼中,確實存在使用不同的 EntityManager 的情況,導致了持久態對象的狀態在不同的 EntityManager 中不同步。具體來說,在 BaseEntityUtils.save(userInfo) 方法中使用了一個 EntityManager 對 userInfo 進行了保存,而在查詢方法 BaseEntityUtils.findById(userInfo.id, UserInfo.class) 中使用了另一個 EntityManager 進行查詢。由于每個 EntityManager 管理著自己的持久化上下文,所以在不同的 EntityManager 中,對相同的實體對象進行的修改不會同步到數據庫。這就解釋了為什么在查詢方法中打印的 new1.user_name 仍然是 "demo123"。要解決這個問題,確保在同一個 EntityManager 中進行保存和查詢操作,以保持持久態對象在相同的上下文中。
改進方案
@Test
public void demo4(){
UserInfo userInfo = new UserInfo();
userInfo.user_name = "demo123";
userInfo.user_sex = 1;
userInfo.user_age = 18;
UserPassword userPassword = new UserPassword();
userPassword.password = "demo-password";
userInfo.userPassword = userPassword;
userPassword.UserPassword_userInfo = userInfo;
UserInfoDomain userInfoDomain = new UserInfoDomain();
userInfoDomain.setDomainEntity(userInfo);
userInfoDomain.save();
System.out.println(userInfo.user_name);
userInfo.user_name = "demo1234";
userPassword.password = "demo1234-password";
userInfoDomain.flush();
UserInfo new1 = userInfoDomain.findById(userInfo.id);
System.out.println(new1.user_name);
}
運行結果為:
Alt text
在UserInfoDomain中,所有的操作都在一個EntityManager里面,所以這兩次打印分別是demo123和demo1234。
在我們提供的代碼中,UserInfoDomain 類的設計確保了所有的操作都在同一個 EntityManager 中,這是非常好的實踐。由于 UserInfoDomain 中的 save() 方法內部使用的是同一個 EntityManager 進行保存操作,保證了持久態對象在相同的上下文中。以下是對我們的代碼的解釋
保存操作
UserInfoDomain userInfoDomain = new UserInfoDomain();
userInfoDomain.setDomainEntity(userInfo);
userInfoDomain.save();
在這里,userInfoDomain.save() 方法內部使用了相同的 EntityManager 進行保存操作。因此,userInfo 對象及其關聯的 userPassword 對象都處于持久態,保存到數據庫。
第一次打印
System.out.println(userInfo.user_name);
這里打印的是 userInfo 對象的 user_name 屬性,即 "demo123"。由于保存操作是在同一個 EntityManager 中執行的,所以在持久態下的 userInfo 對象的屬性是最新的。
修改操作
userInfo.user_name = "demo1234";
userPassword.password = "demo1234-password";
這里對 userInfo 對象進行了屬性的修改。
查詢操作
UserInfo new1 = userInfoDomain.findById(userInfo.id);
在這里,findById 方法內部也是使用了相同的 EntityManager 進行查詢操作。因此,獲取的 new1 對象是 userInfo 對象在數據庫中的最新狀態。
第二次打印
System.out.println(new##### user_name);
這里打印的是經過查詢操作后的 new1 對象的 user_name 屬性,即 "demo1234"。由于查詢操作也是在相同的 EntityManager 中執行的,所以能夠獲取到最新的數據庫狀態。總體來說,通過確保在同一個 EntityManager 中進行保存和查詢操作,我們有效地維持了一致的持久化上下文,確保對象的狀態能夠正確同步到數據庫。這是良好的實踐,可以有效避免對象狀態不同步的問題。