從零開始 Spring Boot 49:Hibernate Entity Lifecycle
圖源:簡書 (jianshu.com)
本文將介紹 Hibernate 的 Session 接口,以及如何用 Session 的相關 API 轉換實體(Entity)的生命周期狀態(tài)。
如果缺少的 JPA 和 Hibernate 的基本認識,可以閱讀前篇文章。
概念
持久化上下文
在 JPA 的相關概念中,存在一個持久化上下文(Persistence Context)。
持久化上下文處于代碼端與數(shù)據(jù)庫之間,充當一個容器或一級緩存的作用,負責管理運行時的實體(Entity),它可以在合適的時間從數(shù)據(jù)庫中加載數(shù)據(jù)到實體對象,也可以將實體對象“回寫”到數(shù)據(jù)庫。
在 Hibernate 中,持久化上下文由 org.hibernate.Session
實例表示,在標準的 JPA 中,表現(xiàn)為jakarta. persistence. EntityManager
。在使用 Hibernate 的時候,這兩者都可以使用,但相比EntityManager
,Session
是一個更豐富的接口,有時候可能會更有用。
實體狀態(tài)
與持久化上下文(本文特指Session
)關聯(lián)的實體實例是存在狀態(tài)的,它們必然處于以下三種狀態(tài)之一:
- transient,此實例從來沒有附加到 Session,且數(shù)據(jù)庫中也不存在對應的行數(shù)據(jù),這只是一個為保存數(shù)據(jù)到數(shù)據(jù)庫創(chuàng)建的新對象。
- persistent,實例與唯一的 Session 對象關聯(lián),并對應數(shù)據(jù)庫中的一條記錄。Session 刷新后,將檢查數(shù)據(jù)一致性,并在不一致的情況下更新數(shù)據(jù)庫中的數(shù)據(jù)。
-
detached,實例曾經(jīng)與 Session 關聯(lián)(處于 persistent 狀態(tài)),但當前已經(jīng)不是。將實例從 Session 逐出(
Session.evict
)、關閉 Session 或將實例序列化/反序列化都會讓實例進入這個狀態(tài)。
可以通過 Session 的 API 將實例的狀態(tài)進行轉換:
圖源:Baeldung
圖中的一些方法調用已經(jīng)作廢,比如
save()
。
持久的實體是最為關鍵的,處于這種狀態(tài)的實體實例會被 Session 管理和監(jiān)控,對這些實體的任何改變都會被記錄,且在事務提交或 Session 關閉時回寫到數(shù)據(jù)庫,且不需要我們調用任何其它方法。
持久實體也被稱作“被管理的實體”(Managed Entity)。實際上這些實體都有一個唯一的數(shù)據(jù)庫標識(database identifier),所以對這些實體的任何更改都會被傳播到數(shù)據(jù)庫。
- 這也是為什么在實體類中要用
@Id
定義一個主鍵字段。- 要牢記,只有在事務提交后才會真正向數(shù)據(jù)庫中插入數(shù)據(jù),但在此之前,也會為持久實體分配數(shù)據(jù)庫標識。
Session API
下面我們看相關的 Session API。
在介紹相關 API 之前,需要對一些用到的工具類進行簡要說明。
@Component
public class HibernateLifecycleUtil {
@SneakyThrows
public List<EntityEntry> getManagedEntities(Session session) {
// 傳入的參數(shù) session 不能是一個代理對象,否則會報類型轉換錯誤
Map.Entry<Object, EntityEntry>[] entries = ((SessionImplementor) session).getPersistenceContext().reentrantSafeEntityEntries();
return Arrays.stream(entries).map(e -> e.getValue()).collect(Collectors.toList());
}
}
HibernateLifecycleUtil.getManagedEntities
方法可以返回 Session 管理的實體實例(persistent)。
public class DirtyDataRecorderInterceptor implements Interceptor, Serializable {
private static final List<Object> dirtyEntities = Collections.synchronizedList(new ArrayList<>());
@Override
public boolean onFlushDirty(Object entity, Object id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) throws CallbackException {
boolean b = Interceptor.super.onFlushDirty(entity, id, currentState, previousState, propertyNames, types);
dirtyEntities.add(entity);
return b;
}
public static List<Object> getDirtyEntities() {
return dirtyEntities;
}
public static void clearDirtyEntitites() {
dirtyEntities.clear();
}
}
DirtyDataRecorderInterceptor
是一個 Hiberante 的攔截器,在這里用于記錄變成“臟數(shù)據(jù)”的實體實例(被修改過的 persistent 實體實例)。
之前的 Hibernate 攔截器往往通過擴展
EmptyInterceptor
實現(xiàn),該類已經(jīng)作廢。
persist
使用persist
方法可以將一個瞬時(transient)的實體實例變成持久的(關聯(lián)到 Session):
Session session = sessionFactory.withOptions().interceptor(new DirtyDataRecorderInterceptor()).openSession();
Transaction transaction = session.beginTransaction();
var student = new Student("icexmoon", LocalDate.of(1990, 1, 1), Gender.MALE);
session.persist(student);
transaction.commit();
session.close();
事務提交后就能看到數(shù)據(jù)庫中多了一條新紀錄。
這里只展示測試用例中的關鍵代碼,可以從這里查看完整代碼。
特別的,如果實體實例已經(jīng)是持久的,那么調用persist
方法不會有任何影響。如果實體實例是分離的,則會產(chǎn)生一個異常:
// 添加實體,實體變成持久化的
session.persist(student);
// 刪除實體,實體變成已分離的
session.evict(student);
// 嘗試添加已分離的實體,會拋出一個PersistentObjectException異常
Assertions.assertThrows(PersistentObjectException.class, () -> {
session.persist(student);
});
merge
使用merge
方法,可以用一個實體實例“更新” Session 中的對應實體實例。
一個典型的用法是在實體實例被分離后,改變其數(shù)據(jù),并合并(merge)回 Session:
List<Student> students = session.createQuery("from user_student", Student.class)
.getResultList();
var icexmoon = students.stream().filter(student -> student.getName().equals("icexmoon"))
.findFirst().get();
// 分離實體,分離后可以對實體進行序列化/反序列化等操作
session.evict(icexmoon);
icexmoon.setBirthDay(LocalDate.of(2000, 5, 1));
Student mergedIcexmoon = session.merge(icexmoon);
// 合并后的 entity 與原始 entity 不是同一個對象,但內容一致
Assertions.assertNotSame(mergedIcexmoon, icexmoon);
Assertions.assertEquals(mergedIcexmoon, icexmoon);
要注意的是,merge
方法被調用后會返回“被管理的實體實例”,且該實例與用于合并的實例并不是同一個對象。前者是持久的,后者依然是分離的。
如果實體實例是瞬時的,調用merge
方法會新建一個持久的實體實例并返回:
// 用一個新的 Entity 添加
Student lalala = new Student("lalala", LocalDate.of(2001, 1, 1), Gender.MALE);
Student mergedLalala = session.merge(lalala);
// 合并后的 entity 與原始 entity 不是同一個對象,但內容一致
Assertions.assertNotSame(mergedLalala, lalala);
Assertions.assertEquals(mergedLalala, lalala);
如果實體實例已經(jīng)是持久的,調用這個方法不會有任何影響。
evict & remove
前邊已經(jīng)多次演示了evict
的用途——將持久實體從 Session 中“驅逐”(變成分離實體)。
要注意的是,evict
僅改變了實體的狀態(tài),并不會影響數(shù)據(jù)庫(不會將對應數(shù)據(jù)刪除):
List<Student> students = session.createQuery("from user_student", Student.class)
.getResultList();
var icexmoon = students.stream().filter(s -> s.getName().equals("icexmoon")).findFirst().get();
session.evict(icexmoon);
transaction.commit();
session.close();
var modifiedStudents = studentRepository.findAll();
Assertions.assertEquals(this.students.size() , modifiedStudents.size());
remove
會將持久實體從 Session 中移除,變成瞬時實體。換言之,會刪除數(shù)據(jù)庫中對應的數(shù)據(jù):
List<Student> students = session.createQuery("from user_student", Student.class)
.getResultList();
var icexmoon = students.stream().filter(s -> s.getName().equals("icexmoon")).findFirst().get();
session.remove(icexmoon);
transaction.commit();
session.close();
var modifiedStudents = studentRepository.findAll();
Assertions.assertEquals(this.students.size() - 1, modifiedStudents.size());
因為remove
后的實體狀態(tài)是瞬時的(transient),所以可以用persist
再次添加:
var icexmoon = students.stream().filter(s -> s.getName().equals("icexmoon")).findFirst().get();
session.remove(icexmoon);
session.persist(icexmoon);
身份域
通常我們并不會直接修改(或添加)實體對象中的身份域(Indentity field),身份域都是由 Session 賦予和管理的。但如果我們愿意,完全可以通過修改和指定身份域去更新指定的持久實體:
var icexmoon = students.stream().filter(s -> s.getName().equals("icexmoon")).findFirst().get();
var student = new Student("icexmoon", LocalDate.of(2002, 1, 1), Gender.MALE);
student.setId(icexmoon.getId());
session.merge(student);
在這個示例中,并沒有像常見的那樣直接修改持久實體icexmoon
中的屬性來變更數(shù)據(jù),而是創(chuàng)建了一個新的瞬時實體student
,并且通過setId
方法為其指定了和持久實體相同的身份域,此時調用merge
方法,就不再是添加一個新的持久實體,而是修改了已有的持久實體(因為它們的 id 相同),并最終修改了數(shù)據(jù)庫中的數(shù)據(jù)。
一般而言實體類不需要 id 的 Setter,這里僅為了實現(xiàn)測試用例而添加。
The End,謝謝閱讀。文章來源:http://www.zghlxwxcb.cn/news/detail-515120.html
可以從這里獲取本文的完整示例代碼。文章來源地址http://www.zghlxwxcb.cn/news/detail-515120.html
參考資料
- Hibernate Entity Lifecycle | Baeldung
- Hibernate: save,persist, update, merge | Baeldung
- 從零開始 Spring Boot 48:JPA & Hibernate - 紅茶的個人站點 (icexmoon.cn)
- What Is the JDK com.sun.proxy.$Proxy Class? | Baeldung
- Dynamic Proxies in Java | Baeldung
- spring boot 中如何設置hibernate Interceptor
- Hibernate Interceptors | Baeldung
- Guide to the Hibernate EntityManager | Baeldung
到了這里,關于從零開始 Spring Boot 49:Hibernate Entity Lifecycle的文章就介紹完了。如果您還想了解更多內容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!