The persistence life cycle

The persistence life cycle

十二月 26, 2019

Entity

Definition

Each persistent instance of an entity class - each entity - represents a unique datastore record.

JPA认两种持久化Java类:EntityEmbeddable。每个Entity类必须声明一个或多个成员变量共同组成其persistent identity又或者叫entity identity,而Embeddable类不需要,因为它们是Entity类的一部分。

Persistent identity本质上是为了解决Java世界与SQL数据库世界对identity定义的不一致性。Java定义了两种确认方式,一种为引用相同即对象的内存地址相同;另一种为对象相等即equals()方法。而数据库使用主键确认两条记录的一致性。JPA引入了persistent identity用于保证一行数据库记录唯一对应一个持久化对象。

EntityManager

Definition

1
2
3
4
5
6
7
8
package javax.persistence;
/**
* Interface used to interact with the persistence context.
*
* <p> An <code>EntityManager</code> instance is associated with
* a persistence context.
*/
public interface EntityManager

EntityManager是应用开发者与JPA runtime交互的基础接口。EntityManager的方法可以从功能上分成6类:

  • 关联事务
    • 在使用Application Managed Transaction时需要开发者手动编程式开启、提交、回滚事务。EntityManager唯一关联一个javax.persistence.EntityTransaction.EntityTransaction用于管理事务。
  • 实体identity管理
    • EntityManager管理当前managed objectsentity identity。它的public <T> T find(Class<T> cls, Object oid);方法会返回给定的persistent identity对应的entity。如果当前persistence context中已经有这个entity的实例则不会发生数据库查询,缓存的实例会直接返回。
  • 缓存管理
    • 此处指的即是平时所说的Hibernate一级缓存,也就是persistence context的缓存。
  • Query的工厂
  • 关闭
    • 当使用的EntityManager是由容器注入(如Spring)时不应关闭EntityManager。如果注入的是EntityManagerFactory并每次创建EntityManager时则应在事务结束后关闭EntityManager。关闭后persistence context也相应结束,被管理的实体也会变为detached状态,所有被其创建的Query实例也会变为invalid状态。
  • 实体生命周期管理

Persistence Context

Definition

A persistence context is a set of entities such that for any persistent identity there is a unique entity instance. Within a persistence context, entities are managed.

另外两个重要的概念为PersistenceUnitPersistenceContextType,也分别对应@PersistenceContext注解中的两个属性。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package javax.persistence;
/**
* Expresses a dependency on a container-managed EntityManager and its associated persistence context.
*/
public @interface PersistenceContext {
// ...
/**
* (Optional) The name of the persistence unit as defined in the
* <code>persistence.xml</code> file. If the <code>unitName</code> element is
* specified, the persistence unit for the entity manager that is
* accessible in JNDI must have the same name.
*/
String unitName() default "";

/**
* (Optional) Specifies whether a transaction-scoped persistence context
* or an extended persistence context is to be used.
*/
PersistenceContextType type() default PersistenceContextType.TRANSACTION;
// ...
}

一般来说PersistenceUnit唯一对应一个数据库,也唯一对应一个EntityManagerFactory(factory的创建成本很高)。而通过EntityManagerFactory创建EntityManager的成本很低。PersistenceUnit描述了其关联的EntityManagerFactory所创建的EntityManager关联的PersistenceContext所能操作的实体的最大范围。

容器注入的EntityManger对应的PersistenceContext的类型默认为TRANSACTIONPersistenceContext随着事务的提交而结束。而EXTENDED类型的则会在EntityManager关闭的时候结束。

Persistence life cycle

Transient

  • 刚用Java new关键词创建出来的实体对象处于transient状态。此时只要应用中不再持有它的引用,这个对象就可以被垃圾回收器回收。Persistence context对这些对象是无感知的。

  • 这些对象可以不具有entity identity,除非选择了应用管理并手动赋值的策略。

Persistent

  • Persistence context管理(managed)的实体。

  • 一个处于persistent state的实体对象在数据库中具有,或将会有一个对应的数据表现。

  • 一定具有entity identity并且与数据库中的对应表现的主键值相同。

为确保对Transient状态的实体调用entityManager.persist()之后也能立即获取到entity identity的值,对于使用了@GeneratedValue委托数据库为entity identity赋值的实体,Hibernate会在调用时立即执行INSERT。这导致了Hibernate无法对插入语句进行批量优化。对于像Oracle这样非原生拥有auto_increment的数据库可以考虑使用Hibernate独有的GeneratorStategy:seqhilo

  • 来源:

    1. 通过Query查询
    2. 通过entityManager.find()identifier lookup
    3. 通过entityManager.persist()Transient状态转化
    4. 通过entityManager.persist()Removed状态恢复
    5. 通过关联mapping的属性CascadeType.PERSIST从父实体级联
    6. 通过访问关联对象的关联引用加载
    7. 通过entityManager.merge()TransientDetached状态转化
  • Persistence context利用entity identity唯一确定一个实体,并将其与唯一确定的一张数据表中的一行数据对应。使用entity identitypersistence context中查找一个实体叫做identifier lookuppersistence context会将缓存的受管理实体直接返回。在使用Query查询数据获取resultSet后,Hibernate也会先通过其中的entity identity执行identifier lookup。若该entity identity对应的实体已经在persistence context中存在,即使数据库中当前数据状态发生了变化(这些变化可以是由其它事务引起的,Read Committed;也可以是由当前事务的Native Query引起的)并与persistence context中实体的状态不一致,Hibernate也不会更新实体的状态。

利用entityManager.getReference()甚至可以在不查询数据的情况下,获得一个象征对应实体的代理对象,直到真正读取该代理对象的数据值时才会触发查询。

  • 处于persist state状态的实体的变化会被感知(通过dirty check),并在flush time与数据库同步

Removed

  • 标记一个实体将会被删除

  • 因为Removed State只能从Persist State转化而来,因此处于此状态的实体也一定都有其entity identity

Hibernate Session#delete提供了删除Detached State实体的功能

Detached

  • 因为Detached State只能从Persist State转化而来,因此处于此状态的实体也一定都有其entity identity

  • 当对一个实体调用entityManager.detach(),或者这个实体所处的persistence context关闭时,这个实体就会变为Detached State

  • entityManager.merge()可以接受一个Detached State的实体,并拷贝其状态然后返回一个managed实体

    为什么被传入的实体在调用entityManager.merge()后还是Detached State?再看一遍Persistence context的定义

  • Hibernate Session#update可以将一个Detached State的实体的对象实例变为Persist State,前提是目前的persistence context中没有具有相同entity identity的实体实例

相关问题

  1. TransientDetached。JPA引入了entity identity用于完成persistence context保证引用唯一的使命。而如果应用需要对不在其管理状态下的实体实例进行操作,就有可能会存在一些问题。最明显的问题之一是equals()hashCode()方法的实现—-应用可能会把实体对象作为key存放在HashMap或利用HashSet去重。

    1. 如果应用始终只对Persist State的实体作这些操作,可以不实现这两个方法—-引用唯一。

      不在事务内的查询每次都会起一个新的persistence context,并在查询操作结束时直接关闭。返回的都是Detached State实体对象

    2. 假如使用场景具有很好很合理的约束,只会对Persist StateDetached State的实体作这些操作;可以考虑使用entity identity—-它在这些状态下一定存在,不可变,且唯一

    3. 对于常规的,使用@GeneratedValue(strategy = GenerationType.IDENTITY)对应MySQL自增主键的实体,在处于Transient State时尚未具有entity identity—-只有调用entityManager.persist()执行INSERT之后才不为null。因此比较好的实现是找到一个永远非空,永远不变的值(业务主键)作为equals()hashCode()的实现依据

    4. 另外要注意的是,equals()方法中不应使用this.getClass() == that.getClass(),而需要用instanceof。实体对象有时候是会以被代理包裹的形式出现的,此时就有可能引起问题。比如将一个被代理的实体对象放入HashMap

  2. Persistent

    1. Persistence context最大的功能和目的是保证管理下的实体引用唯一,且尽量将数据库修改操作推后。
    2. Dirty check在这里起到了举足轻重的作用。Dirty check的时机是EntityManager进行flush的时候。除了手动触发,flush会在persistence context关闭时对所有实体,或使用java.persistence.Query进行查询前对要查询的实体类的实例自动触发。在比较大的数据量,或循环进行很多次查询的情况下,Dirty Check很可能会带来很大的CPU负载
    3. Hibernate会在查询转化数据库返回结果时保存一份实体的快照,在进行Dirty Check与实体实例当前的状态进行比较。
    4. 比较有用的知识是,java.persistence.Column注解的updatable属性可以用于标记字段是否可变。不可变的字段不会被进行Dirty check。而Hibernate更是提供了org.hibernate.annotations.Immutable注解标记整个实体或实体集合是否为不可变
    5. 另外,org.springframework.transaction.annotation.TransactionalreadOnly属性,可以使该事务变为只读事务。不光会启用数据库的只读事务,同时也会改变JPA进行flush的行为,变为不再自动flush,也就避免了Dirty Check