bigywater.tech
Java · 416

JPA Entity 生命周期

JPA 的实体有四种状态和七个回调,但它们在什么时候发生、与 SQL 的先后关系、回调里能做什么,规范只给出了概要。这篇用真实的 SQL 日志把每一步量出来,包括几个会让人吃惊的边界。

478 次 / 951 条
@PreUpdate 里执行 JPQL 时回调被递归进入的次数 / 这次 flush 发出的 SQL 数(最后 StackOverflowError)
persist 时 vs flush 时
@PostPersist 的触发时刻:IDENTITY 主键在 persist 时,SEQUENCE 主键在 flush 时
false
@PrePersist 抛异常之后 commit() 没抛异常,但同一事务里更早 persist 的实体是否被保存

状态转换

实测 · 本机执行输出
  new A:contains = false,id = null
      回调 A(IDENTITY) @PrePersist
      SQL> insert into A (name,touched) values (?,?)
      回调 A(IDENTITY) @PostPersist
  persist 之后:contains = true,id = 2
  对托管对象再次 persist:新增 SQL 0 条(无操作)
      SQL> select a1_0.id,a1_0.name,a1_0.touched from A a1_0 where a1_0.id=?
      回调 A(IDENTITY) @PostLoad
  find 后 detach:contains = false
    对游离对象 persist → EntityExistsException:detached entity passed to persist: A
    对游离对象 remove → IllegalArgumentException:Removing a detached instance A#1
      SQL> select a1_0.id,a1_0.name,a1_0.touched from A a1_0 where a1_0.id=?
      回调 A(IDENTITY) @PostLoad
      回调 A(IDENTITY) @PreRemove
  remove(托管对象) 之后:contains = false(removed 状态,contains 返回 false),SQL 还没发
  对 removed 对象再次 persist:contains = true(回到 managed,新增 SQL 0 条,不会 DELETE 也不会 INSERT)
      回调 A(IDENTITY) @PreRemove
    对 removed 对象 merge → IllegalArgumentException:org.hibernate.ObjectDeletedException: deleted instance passed to merge: [A#<null>]
    对 new 对象 remove → 没有异常
    persist(null) → IllegalArgumentException:attempt to create event with null entity

回调与 SQL 的时机

实测 · 本机执行输出
  persist(new A) —— IDENTITY:
      回调 A(IDENTITY) @PrePersist
      SQL> insert into A (name,touched) values (?,?)
      回调 A(IDENTITY) @PostPersist
  persist(new B) —— SEQUENCE:
      SQL> select nextval('b_seq')
      回调 B(SEQUENCE) @PrePersist
  flush():
      SQL> insert into B (name,touched,id) values (?,?,?)
      回调 B(SEQUENCE) @PostPersist
  find(A, 1) / 修改 touched / flush:
      SQL> select a1_0.id,a1_0.name,a1_0.touched from A a1_0 where a1_0.id=?
      回调 A(IDENTITY) @PostLoad
      回调 A(IDENTITY) @PreUpdate
      SQL> update A set name=?,touched=? where id=?
      回调 A(IDENTITY) @PostUpdate
  remove(A 1) / flush:
      回调 A(IDENTITY) @PreRemove
      SQL> delete from A where id=?
      回调 A(IDENTITY) @PostRemove
  commit:

回调里改字段

实测 · 本机执行输出
      SQL> select c1_0.id,c1_0.modified,c1_0.name,c1_0.note from C c1_0 where c1_0.id=?
      SQL> update C set modified=?,name=?,note=? where id=?
      回调 C @PostUpdate 里改了 note = set-in-PostUpdate
  flush 之后内存里:modified = 2026-01-01T00:00:00Z,note = set-in-PostUpdate(此时库里的 note 还是旧值,因为 @PostUpdate 在 UPDATE 之后才改它)
      SQL> update C set modified=?,name=?,note=? where id=?
      回调 C @PostUpdate 里改了 note = set-in-PostUpdate
  commit 时又发出 1 条 UPDATE(实体因为 @PostUpdate 里的修改又变脏了),@PostUpdate 又被调用一次
      SQL> select c1_0.id,c1_0.modified,c1_0.name,c1_0.note from C c1_0 where c1_0.id=?
  重新加载:modified = 2026-01-01T00:00:00Z,note = set-in-PostUpdate(两个回调里的修改最终都保存了,但 @PostUpdate 的那个靠的是第二条 UPDATE)

限制与异常

实测 · 本机执行输出
      回调 A(IDENTITY) @PrePersist
      SQL> insert into A (name,touched) values (?,?)
      回调 A(IDENTITY) @PostPersist
  @PrePersist 里抛 IllegalStateException:persist 抛出 IllegalStateException:name 不能为空(@PrePersist 校验);事务 isActive = true,getRollbackOnly = true
  之后 commit():没有抛异常
      SQL> select count(a1_0.id) from A a1_0 where a1_0.name='keep-me'
  同一个事务里更早 persist 的 A("keep-me")最终是否保存 = false(事务被标记为只能回滚,commit 没有提交它)
      SQL> select d1_0.id,d1_0.name from D d1_0 where d1_0.id=?
      回调 D @PreUpdate 里执行 JPQL 失败:RuntimeException:java.lang.StackOverflowError
      SQL> update D set name=? where id=?
  @PreUpdate 里使用 EntityManager 执行 JPQL 查询之后 flush 完成:@PreUpdate 被进入了 478 次(查询触发了自动 flush,flush 又触发 @PreUpdate),这次 flush 一共发出 951 条 SQL
      回调 A(IDENTITY) @PrePersist
    没有事务时 persist(IDENTITY 主键的 A) → 没有异常
    没有事务时 flush() → TransactionRequiredException:no transaction is in progress
      SQL> select nextval('b_seq')
      回调 B(SEQUENCE) @PrePersist
    没有事务时 persist(SEQUENCE 主键的 B) → 没有异常
    之后 flush() → TransactionRequiredException:no transaction is in progress

分步演示

JPA Entity 生命周期
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK(Temurin 17.0.20、Homebrew 21.0.12 与 27,每篇注明实际用的版本);用 javac 编译、java 直接运行,需要第三方库时用 Maven 解析依赖;计时用 System.nanoTime,数字每次运行会有小幅抖动。Hibernate ORM 6.6.4.Final、PostgreSQL JDBC 42.7.4、本机 PostgreSQL 16.15(端口 9501);SQL 来自 hibernate.show_sql;@PreUpdate 递归次数取决于栈深度,几次运行在 477–479 之间
    • 没有验证:Spring 的 @Transactional 与实体监听器(@EntityListeners)的差别、cascade(PERSIST / REMOVE / MERGE)与 orphanRemoval、@Version 实体的 merge、Hibernate 的 Interceptor / Session 事件监听器、其它 JPA 实现的回调顺序
    京ICP备2026067502号-1京公网安备11010502063487号