JPA 里“改了对象就自动保存”不是魔法,而是持久化上下文的脏检查。这篇用真实的 SQL 日志与计时量出:它什么时候发 UPDATE、什么时候不发,一级缓存的身份映射,游离对象与 merge,flush 的时机,以及托管实体变多之后的代价。
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
find 之后:SQL 语句数 +1,em.contains(p) = true,age = 30
p.age = 31(没有调用任何持久化方法);在 commit 之前 SQL 语句数 +1
SQL> update P set age=?,name=? where id=?
commit 之后新增 SQL 语句 1 条(更新语句,见上面的 Hibernate: update …)
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
把 age 赋成相同的值再 commit:新增 UPDATE 0 条(只比较值,不看是否调用过 setter)
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
改成 99 又改回原值再 commit:新增 UPDATE 0 条(与加载时的快照比较,中间状态不重要)
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
批量更新绕过持久化上下文:内存里 bob 的 age = 40
SQL> update P p1_0 set age=70 where p1_0.id=2
JPQL update 影响 1 行;内存里的 p.age 仍是 40,数据库里已经是 70
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
em.refresh(p) 之后 p.age = 70 SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
同一个 EntityManager 里 find 两次:x1 == x2 为 true,SQL 语句数 +1
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=1
再用 JPQL 查同一行:返回的对象 == x1 为 true,SQL 语句数 +2(查询仍然发出了 SQL,但返回的是已托管的那个对象)
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
另一个 EntityManager 里 find 同一行:y == x1 为 false,y.equals(x1) 为 false(没有重写 equals,所以是不同对象)
getReference(2):新增 SQL 语句 0 条(没有查询),对象类 = P(Hibernate 生成的代理子类),Hibernate.isInitialized = false
直接读代理对象的字段 ref.name = null(代理自己的字段是 null,没有触发加载),Hibernate.isInitialized = false
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
调用 ref.getName() = bob:新增 SQL 语句 1 条,Hibernate.isInitialized = true SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
detach 之后改 age = 500,commit:新增 UPDATE 0 条,contains = false
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
把游离对象 d 交给 merge:返回对象 merged 与 d 是否同一个 = false,merged 是否托管 = true,d 是否托管 = false,merged.age = 500
SQL> update P set age=?,name=? where id=?
commit:新增 UPDATE 1 条;之后再改 d.age = 1(d 仍然游离)不会有任何效果
新建的 P:persist 前 id = null,contains = false
SQL> insert into P (age,name) values (?,?)
persist 之后 id = 3(IDENTITY 策略要立刻 INSERT 才能得到 id),contains = true,SQL 语句数是否已增加:是 SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
SQL> update P set age=?,name=? where id=?
SQL> select count(p1_0.id) from P p1_0 where p1_0.age=71
FlushMode = AUTO:改了 bob 的 age = 71 之后立刻 JPQL 查询 count(age = 71) = 1,查询时新增 SQL 语句 2 条(先 UPDATE 再 SELECT)
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 where p1_0.id=?
SQL> select count(p1_0.id) from P p1_0 where p1_0.age=71
FlushMode = COMMIT:改了 bob 的 age = 71 之后立刻 JPQL 查询 count(age = 71) = 0,查询时新增 SQL 语句 1 条(只有 SELECT) SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 order by p1_0.id fetch first ? rows only
SQL> update P set age=?,name=? where id=?
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 order by p1_0.id fetch first ? rows only
SQL> update P set age=?,name=? where id=?
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 order by p1_0.id fetch first ? rows only
SQL> update P set age=?,name=? where id=?
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 order by p1_0.id fetch first ? rows only
SQL> update P set age=?,name=? where id=?
1000 个托管实体:没有任何修改时 flush 约 0.08 ms(31 次中位数,第二轮,JIT 已预热);改其中 1 个后 flush 1.19 ms;em.clear() 之后 flush 0.003 ms
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 order by p1_0.id fetch first ? rows only
SQL> update P set age=?,name=? where id=?
10000 个托管实体:没有任何修改时 flush 约 0.71 ms(31 次中位数,第二轮,JIT 已预热);改其中 1 个后 flush 1.13 ms;em.clear() 之后 flush 0.002 ms
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 order by p1_0.id fetch first ? rows only
SQL> update P set age=?,name=? where id=?
50000 个托管实体:没有任何修改时 flush 约 3.70 ms(31 次中位数,第二轮,JIT 已预热);改其中 1 个后 flush 4.82 ms;em.clear() 之后 flush 0.005 ms
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 order by p1_0.id fetch first ? rows only
SQL> select p1_0.id,p1_0.age,p1_0.name from P p1_0 order by p1_0.id fetch first ? rows only
50000 个以只读方式加载的实体(hint org.hibernate.readOnly = true):flush 约 2.87 ms(31 次中位数)
只读加载的实体被修改后 flush:新增 SQL 语句 0 条(只读实体的修改不会被写回)