“一级缓存让查询不再访问数据库”只对一小部分操作成立,而且它带来的陈旧数据和内存问题常被忽略。这篇用真实的 SQL 计数、另一个连接的并发修改和 10 万行的堆内存测量,量出它的行为。
find(1) 第一次:+1 find(1) 第二次:+0(命中一级缓存) 再 find 两次:+0 JPQL 按主键查同一行:+1(JPQL 总是访问数据库) JPQL 按唯一字段 code 查同一行:+1 getReference(1)(已经在缓存里):+0 find 一个不存在的主键(999)三次:+3(不存在的行没有“负缓存”,每次都查) count 聚合查询:+1 先用 JPQL 把 bob 查出来,再 find(2):+0,q == q2 为 true(JPQL 的结果也会放进一级缓存)
加载 alice:age = 30,name = alice (另一个连接已经提交:age = 99,name = ALICE-DB) find(1):age = 30,name = alice(缓存里的旧值) JPQL 重新查询(SQL 确实发给了数据库):返回对象 == 缓存里的对象为 true,z.age = 30,z.name = alice(数据库里已经是 99,但返回的还是缓存里的旧实体) 只查字段(投影)select p.age, p.name:99, ALICE-DB(投影直接读数据库,看到新值) em.refresh(x) 之后:age = 99,name = ALICE-DB em.clear() 后再 find:age = 100(重新从数据库加载)
EntityManager 1 加载 alice:age = 10 (另一个连接把 age 改成 77 并提交) EntityManager 1 只改了 name,提交之后库里:age = 10,name = alice-renamed(UPDATE 带上了所有列,把另一个事务写的 age = 77 覆盖回了 10)
加载前堆内存 10 MiB 托管实体不清理(一个 EntityManager 全部留着):读完 100002 行,堆内存 92 MiB(+82 MiB),耗时 723 ms,em.contains 随机一个 = true 每 1000 个 em.clear():读完 100002 行,堆内存 10 MiB(+0 MiB),耗时 526 ms StatelessSession(没有一级缓存):读完 100002 行,堆内存 10 MiB(+0 MiB),耗时 343 ms