知其然,知其所以然
Spring 全家桶 2026-10-20

Observation API:一个注解同时得到指标与链路

实测 Spring Boot 4.0.8 的 Observation API:一次 HTTP 请求同时产生指标和链路,日志自动带 traceId;低基数标签进指标也进 span,高基数标签只进 span;@Observed 一个注解同时得到 Timer 指标和 span;默认采样率下 100 个请求只保留 10 个 span,指标不受采样影响;按名字关闭观察会让指标和 span 一起消失。

Spring Boot Observation Micrometer 链路追踪 指标 真实验证 动画演示
Spring 全家桶 2026-10-20

Repository 接口为什么不用写实现:代理与 SimpleJpaRepository

实测 Spring Data JPA 的 Repository 接口为什么不用写实现:容器里是 JDK 动态代理 $Proxy107,Bean 定义是 JpaRepositoryFactoryBean,目标对象是 SimpleJpaRepository,代理上挂了 7 个拦截器;findAll/findById/count 在只读事务里,save 在可写事务里,派生查询与自定义片段没有事务。

Spring Data JPA Repository 动态代理 事务 真实验证 动画演示
Spring 全家桶 2026-10-20

派生查询:方法名怎么变成 SQL

实测 Spring Data JPA 派生查询的方法名到 SQL 的转换:And/Or/Between/In/Top/First/Exists/Count 各自对应的 SQL;Containing 和 StartingWith 会转义 %;IgnoreCase 变成 upper() 比较;deleteBy 先 select 再逐行 delete(3 行就是 1 + 3 条 SQL);List<String> 的派生查询会转换失败;方法名写错时启动就失败。

Spring Data JPA 派生查询 方法名 真实验证 动画演示
Spring 全家桶 2026-10-20

@Query 与分页:count 查询与排序的坑

实测 Spring Data JPA 的 @Query 与分页:Page 是数据加 count 两条 SQL,Slice 和 List 只有一条;内容不足一页时可以省掉 count;原生查询的 Sort 要用列名;排序不唯一翻 50 页只读到 896 个不同行;offset 499980 的 Slice 要 91.7 ms 而 keyset 一直 0.7 ms;join fetch 加分页在内存里分页且 count 数的是 join 行数。

Spring Data JPA 分页 Query count 排序 真实验证 动画演示
Spring 全家桶 2026-10-20

Specification 与动态查询

实测 Spring Data JPA 4 的 Specification:and/or/not 的 SQL;where(null) 与 and(null) 不再允许,用 Specification.unrestricted();三个可选条件 8 种组合都是一条 SQL;分页的数据与 count 用同一份条件;带 join 的集合不加 distinct,分页会少人(内容 1 行、总数 1,实际 2);用户输入里的 % 是通配符,注入则被参数绑定挡住。

Spring Data JPA Specification 动态查询 Criteria 真实验证 动画演示
Spring 全家桶 2026-10-20

N+1 与 @EntityGraph、JOIN FETCH

实测 Spring Data JPA 的 N+1:200 个作者的 findAll 再遍历 books 发出 201 条 SQL,@ManyToOne 默认 EAGER 也是 201 条;join fetch、@EntityGraph、DTO 投影都是 1 条 SQL(25.9 ms 降到 7.5 ms);两个 List 一起 fetch 抛 MultipleBagFetchException,两个 Set 会得到 300 行笛卡尔积;hibernate.default_batch_fetch_size=50 把 201 条变成 5 条。

Spring Data JPA N+1 EntityGraph JOIN FETCH Hibernate 真实验证 动画演示
Spring 全家桶 2026-10-20

Open-In-View:数据库连接被占到响应结束

实测 Spring Boot 4.0.8 的 spring.jpa.open-in-view:默认打开并在启动时给出 WARN;打开时数据库连接从第一次查询一直被持有到请求结束(查库返回之后、睡 600 ms 之后活动连接都是 1);关闭后懒加载会在事务外抛 LazyInitializationException;池只有 2 个连接时,打开比关闭的吞吐低约 4.5 倍并出现 17 个超时 500。

Spring Data JPA Open-In-View HikariCP 连接池 真实验证 动画演示
Spring 全家桶 2026-10-20

@Version 乐观锁与重试

实测 JPA 的 @Version:每次更新是 where id=? and version=?,撞车时命中 0 行抛 ObjectOptimisticLockingFailureException;8 个线程各加 25 次,没有 @Version 少了 169、有 @Version 不重试放弃 169 次、重试后一个不丢(冲突 72 次);批量 JPQL 更新绕过 @Version,要写 update versioned;表单场景里丢掉旧 version 会静默覆盖。

Spring Data JPA 乐观锁 Version 重试 并发 真实验证 动画演示
Spring 全家桶 2026-10-20

审计:@CreatedDate 与 @LastModifiedBy

实测 Spring Data JPA 审计:@EnableJpaAuditing 加实体上的 @EntityListeners 与四个注解;更新时只改 last_modified_*;同值修改与只读不会更新审计字段,@Modifying 批量 update 会漏掉;AuditorAware 返回空时 createdBy 为 null 且不报错;换线程后 ThreadLocal 里的用户丢失;缺 @EnableJpaAuditing 或 @EntityListeners 都是静默不填。

Spring Data JPA 审计 Auditing CreatedDate 真实验证 动画演示
Spring 全家桶 2026-10-20

JdbcTemplate 与 NamedParameterJdbcTemplate:RowMapper 与批量

实测 Spring JdbcTemplate、NamedParameterJdbcTemplate 与 JdbcClient:queryForObject 的 0 行与多行异常;BeanPropertyRowMapper 对不上的列静默为 null;IN 空集合变成 in () 报语法错误;batchUpdate 在默认的 MySQL 参数下仍然发出 5000 条 INSERT(517 ms 对循环的 614 ms);update 的返回值是匹配的行数;忘了 RETURN_GENERATED_KEYS 时 INSERT 已经执行。

Spring JdbcTemplate NamedParameterJdbcTemplate RowMapper JdbcClient 批量 真实验证 动画演示
Spring 全家桶 2026-10-20

批量写入:saveAll、batchUpdate 与 rewriteBatchedStatements

实测 10000 行批量插入:默认参数下 batchUpdate 与循环一样发出 10000 条 INSERT;加 rewriteBatchedStatements=true 后 batchUpdate 变成 1 条多行 insert,45 ms;JPA saveAll 在 IDENTITY 主键下 batch_size 不起作用(10000 条 INSERT);手动主键不实现 Persistable 时 saveAll 每行先 select;手动主键加 Persistable 加 rewrite 后 94 ms、20 条 INSERT。

Spring Data JPA 批量写入 saveAll batchUpdate rewriteBatchedStatements 真实验证 动画演示
Spring 全家桶 2026-10-20

HikariCP 在 Boot 里的默认值,以及池耗尽时的症状

实测 Spring Boot 4.0.8 里 HikariCP 的默认值:最大 10、最小空闲 10、连接超时 30000 ms、最大寿命 1800000 ms、keepaliveTime 120000;池耗尽时请求等满 connection-timeout 后抛 CannotGetJdbcConnectionException;leak-detection-threshold 小于 2000 ms 会被直接关掉;池大小就是数据库并发度(2、4、10、32 个连接对应 92、176、413、1187 次/秒)。

Spring Boot HikariCP 连接池 池耗尽 泄漏检测 真实验证 动画演示
京ICP备2026067502号-1京公网安备11010502063487号