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