从零开始 Spring Boot 49:Hibernate Entity Lifecycle
本文将介绍 Hibernate 的 Session 接口,以及如何用 Session 的相关 API 转换实体(Entity)的生命周期状态。
如果缺少的 JPA 和 Hibernate 的基本认识,可以阅读前篇文章。
概念
持久化上下文
在 JPA 的相关概念中,存在一个持久化上下文(Persistence Context)。
持久化上下文处于代码端与数据库之间,充当一个容器或一级缓存的作用,负责管理运行时的实体(Entity),它可以在合适的时间从数据库中加载数据到实体对象,也可以将实体对象“回写”到数据库。
在 Hibernate 中,持久化上下文由 org.hibernate.Session
实例表示,在标准的 JPA 中,表现为jakarta. persistence. EntityManager
。在使用 Hibernate 的时候,这两者都可以使用,但相比EntityManager
,Session
是一个更丰富的接口,有时候可能会更有用。
实体状态
与持久化上下文(本文特指Session
)关联的实体实例是存在状态的,它们必然处于以下三种状态之一:
- transient,此实例从来没有附加到 Session,且数据库中也不存在对应的行数据,这只是一个为保存数据到数据库创建的新对象。
- persistent,实例与唯一的 Session 对象关联,并对应数据库中的一条记录。Session 刷新后,将检查数据一致性,并在不一致的情况下更新数据库中的数据。
- detached,实例曾经与 Session 关联(处于 persistent 状态),但当前已经不是。将实例从 Session 逐出(
Session.evict
)、关闭 Session 或将实例序列化/反序列化都会让实例进入这个状态。
可以通过 Session 的 API 将实例的状态进行转换:
图源:Baeldung
图中的一些方法调用已经作废,比如
save()
。
持久的实体是最为关键的,处于这种状态的实体实例会被 Session 管理和监控,对这些实体的任何改变都会被记录,且在事务提交或 Session 关闭时回写到数据库,且不需要我们调用任何其它方法。
持久实体也被称作“被管理的实体”(Managed Entity)。实际上这些实体都有一个唯一的数据库标识(database identifier),所以对这些实体的任何更改都会被传播到数据库。
- 这也是为什么在实体类中要用
@Id
定义一个主键字段。- 要牢记,只有在事务提交后才会真正向数据库中插入数据,但在此之前,也会为持久实体分配数据库标识。
Session API
下面我们看相关的 Session API。
在介绍相关 API 之前,需要对一些用到的工具类进行简要说明。
@Component
public class HibernateLifecycleUtil {
@SneakyThrows
public List<EntityEntry> getManagedEntities(Session session) {
// 传入的参数 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<>());