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