知其然,知其所以然
Java 内部原理 2026-10-05

JPA 乐观锁与悲观锁

在 Hibernate 6.6.4 + 真实 PostgreSQL 上实测 JPA 乐观与悲观锁:@Version 的 UPDATE 带 where version = ?,冲突抛 OptimisticLockException;PESSIMISTIC_WRITE 是 for no key update,nowait / skip locked 可用,但大于 0 的锁超时被忽略;死锁约 1 秒后检测并被包装成 OptimisticLockException;8 线程计数器:无锁丢失 1347 / 1600 次,乐观重试 6216 次,悲观最快。

Java JPA Hibernate 乐观锁 悲观锁 死锁 PostgreSQL 真实验证 动画演示
Java 内部原理 2026-10-05

JPA Criteria API:程序化构造类型化查询

在 Hibernate 6.6.4 + 真实 PostgreSQL 上实测 JPA Criteria API:与 JPQL 发出同样的 SQL(值是绑定参数,cb.literal 才内联);字符串属性名拼错与类型错误在运行时才报错,静态元模型把拼写错误提前到编译期;每次重新构建比 JPQL 慢约 25%(77.0 vs 61.5 µs)且不经过查询计划缓存;8 种动态条件组合产生 8 种 SQL 文本。

Java JPA Hibernate Criteria API 静态元模型 动态查询 PostgreSQL 真实验证 动画演示
Java 内部原理 2026-10-05

AttributeConverter:实体字段与数据库值之间的转换

在 Hibernate 6.6.4 + 真实 PostgreSQL 上实测 AttributeConverter:autoApply 也应用到基本类型 boolean;转换器要自己处理 null,空集合存成空串;原地修改被转换的 Set 能被脏检查发现;加载 1000 个实体 toEntity 被调用 3000 次;参数与字面量都会转换,like 不能用在被转换的字段上;库里出现无法识别的值会让读到它的查询失败。

Java JPA Hibernate AttributeConverter 类型转换 PostgreSQL 真实验证 动画演示
Java 内部原理 2026-10-05

JPA N+1 查询:对象导航如何变成大量 SQL

在 Hibernate 6.6.4 + 真实 PostgreSQL 上实测 JPA N+1:1000 个作者逐个访问 books 是 1001 条 SQL(201.5 ms);@ManyToOne 默认 EAGER 让 JPQL 也 N+1;join fetch / EntityGraph / DTO 为 1 条 SQL,@BatchSize 41 条,SUBSELECT 2 条;两个 List 同时 fetch 抛 MultipleBagFetchException,Set 得到 20000 行的笛卡尔积;带集合 fetch 的分页在内存里读入 6000 个实体。

Java JPA Hibernate N+1 join fetch BatchSize EntityGraph PostgreSQL 真实验证 动画演示
Java 内部原理 2026-10-05

JAX-RS Filter:在 HTTP 资源前后插入处理

用 Jersey 3.1.9 + JDK HTTP 服务器实测 JAX-RS 过滤器:请求过滤器按 @Priority 升序、响应过滤器降序;@PreMatching 能改写路径与方法;abortWith 跳过资源但响应过滤器照常运行;过滤器抛出的异常经 ExceptionMapper 映射成 500;过滤器读取请求体不放回会让资源收到空 body。

Java JAX-RS Jersey 过滤器 ContainerRequestFilter ExceptionMapper 真实验证 动画演示
Java 内部原理 2026-10-05

JMS ACK 模式:消息何时算被确认

用嵌入式 ActiveMQ Artemis 2.37.0 实测 JMS 确认模式:AUTO_ACKNOWLEDGE 在 receive() 返回时确认,消费者崩溃会丢消息;CLIENT_ACKNOWLEDGE 的 acknowledge() 确认会话已收到的全部消息;监听器抛异常会重新投递;事务会话 rollback 立刻重新投递,生产者事务 commit 才可见;超过最大投递次数进入死信队列,但死信队列不存在时消息直接消失。

Java JMS ActiveMQ Artemis 确认模式 消息队列 死信队列 真实验证 动画演示
Java 内部原理 2026-10-05

Jakarta WebSocket Endpoint 生命周期

用 Jetty 12.0.16 的 Jakarta WebSocket 服务端与 JDK 客户端实测 Endpoint 生命周期:每个连接一个实例,@OnOpen → @OnMessage → @OnClose;abort 得到 @OnError + 1006;@OnMessage 抛异常得到 CloseException 与 1003;空闲 804 ms 后 1001;100,000 字符超过默认 64 KiB 得到 1009;并发 sendText 在 Jetty 上不报错。

Java WebSocket Jakarta WebSocket Jetty 生命周期 关闭码 真实验证 动画演示
Java 内部原理 2026-10-05

JUnit Nested Test:按场景组织测试层次

用 JUnit 5.11.3 实测 @Nested:每个测试重新构造整条外 → 内的实例链,5 个测试共 10 次 @BeforeEach、10 次构造;控制台启动器不能直接选择嵌套类(--select-class / --select-method 都是 0 个测试,--include-classname 只对顶层类生效);没有 @Nested 的静态嵌套类在选择外层类时不运行;@ExtendWith、@Tag、@Disabled 向内层继承。

Java JUnit 5 Nested 测试组织 生命周期 真实验证 动画演示
Java 内部原理 2026-10-05

JUnit 并行测试

用 JUnit 5.11.3 实测并行测试:16 个 300 ms 的 I/O 测试从串行 4966 ms 降到 fixed 并行度 16 的 360 ms;只有类并发时类内方法仍串行(1288 ms);CPU 型测试在 10 核机器上并行度 10 → 16 只有 834 → 795 ms;@ResourceLock 同一个键让 16 个测试变回完全串行(5005 ms);@Isolated 与 @Execution(SAME_THREAD) 的影响。

Java JUnit 5 并行测试 ForkJoinPool ResourceLock 真实验证 动画演示
Java 内部原理 2026-10-05

Flaky Test 治理

实测不稳定测试:单例竞争在空闲时失败 9.63%、满载时只有 0.45%,时序断言空闲 0%、满载 72.67%,顺序依赖 16.15% 与负载无关;最多重试 3 次把 167 次真实竞争失败中的 166 次变成“通过”;JUnit 重试扩展里 aborted 的次数就是被掩盖的失败;@Tag("quarantine") 把不稳定测试从门禁里拿出来。

Java JUnit 不稳定测试 flaky 重试 隔离 真实验证 动画演示
Java 内部原理 2026-10-05

覆盖率指标的误区

用 JaCoCo 0.8.12 与 PIT 1.17.1 在同一份代码上实测覆盖率的误区:没有任何断言的测试拿到行覆盖 88%、分支覆盖 100%、变异得分 0%;带分支与边界断言的测试同样 88% / 100%,变异得分 100%;一行代码 100% 行覆盖时分支覆盖可以只有 2 / 4;JaCoCo 探针只让 CPU 型循环慢 1.03 倍。

Java 测试 代码覆盖率 JaCoCo 变异测试 PIT 真实验证 动画演示
Java 内部原理 2026-10-05

时区与 Locale 测试

在 JDK 17 / 21 / 27 上实测时区与 Locale 对测试的影响:user.language 在启动后改变不起作用而 Locale.setDefault 立即生效("title".toUpperCase() 在 tr-TR 下变成 TİTLE);缓存的 Clock 不会跟随新的默认时区;夏令时当天有 23 或 25 小时;JDK 17 与 21 的本地化输出有 1 / 7 行不同。

Java 测试 时区 Locale 夏令时 CLDR 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号