《Java 编程实战》8.1 参数校验:Jakarta Validation 与手写规则
libraryd 的 bodyAsClass 只负责解析,不负责判断字段是否为空、超长或越界。本节用 Jakarta Validation 3.1.1 与 Hibernate Validator 9.1.5 把约束写在 record 上,把 ConstraintViolation 收成字段错误 Map,并用真实输出说明 Expressly 缺失、null 与空白、消息语言和领域校验的分工。
tag
libraryd 的 bodyAsClass 只负责解析,不负责判断字段是否为空、超长或越界。本节用 Jakarta Validation 3.1.1 与 Hibernate Validator 9.1.5 把约束写在 record 上,把 ConstraintViolation 收成字段错误 Map,并用真实输出说明 Expressly 缺失、null 与空白、消息语言和领域校验的分工。
进程收到 SIGTERM 时,libraryd 正在处理的借阅请求会不会被切断,连接池和定时任务会不会来不及收尾。本节用两次真实停机实验对比 stopTimeout=0 与 10 秒的差别,再给出 LibraryApplication 的关闭顺序、幂等关闭、启动失败回滚和 shutdown hook 的完整写法。
libraryd 的每个 handler 都在重复读请求头、拼日志、记耗时。本节用 Javalin 的 before/after 把请求 ID、MDC、访问日志和指标收进中间件,并用真实执行顺序说明异常处理器与 after 的先后关系,最后给出请求头信任、MDC 清理与指标基数的边界。
libraryd 的服务层已经能借书还书,却还没有 HTTP 入口。本节用 Javalin 7.2.4 把路由挂在配置对象上,接通路径参数、查询参数、分页默认值与 Jackson,并用真实请求验证 201、204、Location、日期序列化和内容类型检查,最后给出完整路由清单。
审计事件要按类型还原,领域对象互相引用又会让 Jackson 无限递归。本节真实复现多态类型丢失、嵌套深度 1002 的报错,并演示如何把事件安全写入 PostgreSQL 的 jsonb 列、再用宽松 mapper 读回,最后给出 libraryd 的 AuditEvent 与仓储写法。
领域 record 不是 API 契约:Loan 没有 status 和 overdueDays,请求直接绑定领域对象又会带来批量赋值。本节用 Jackson 真实输出设计 BookDto、LoanDto、PageResponse 与 ApiError,划清请求、响应和领域对象的边界。
HTTP 层不能靠字符串拼接生成 JSON。本节用 Jackson 2.22.4 真实演示 record 绑定、命名策略、JavaTimeModule 与 ISO-8601 日期,以及 FAIL_ON_UNKNOWN_PROPERTIES 的报错原文,最后把配置收进 libraryd 的单例 JsonSupport。
两个借阅请求同时通过「没有活动借阅」检查时,应用层的先查再插并不安全。本节用两个 JDBC 连接真实复现部分唯一索引冲突、条件更新幂等归还与死锁 40P01,并给出 libraryd 借阅、归还和批量写的最终写法,说明为什么 synchronized 在多实例下无效。
两个连接同时读写 loans 时,read committed 与 repeatable read 到底差在哪?本节用真实 PostgreSQL 输出拆开语句快照与事务快照,观察 xmin/xmax 与行锁等待,并给出 libraryd 报表查询与可重试事务的落地写法。
libraryd 的一次借阅要写会员和借阅两条 SQL,JDBC 默认的自动提交会让失败只回滚一半。本节用真实 PostgreSQL 演示 autoCommit=false、commit、rollback 与保存点,把事务边界收在服务方法上,并解释 25P02 与连接归还问题。
列表页、批量导入和借阅详情会把数据访问层的问题放大。本节实现 LIMIT/OFFSET 分页与总数统计,比较逐条插入、addBatch 和 reWriteBatchedInserts 的真实耗时,并用一次 JOIN 消灭活动借阅列表里的 N+1 查询。
小数据量下所有查询都很快,索引问题要等到十万行才暴露。本节用 EXPLAIN (ANALYZE, BUFFERS) 对比 Seq Scan、Index Scan 与 Bitmap Index Scan,给 libraryd 的模糊搜索和活动借阅补上 pg_trgm GIN 索引与部分索引。
数据库 schema 是 libraryd 的最终契约,也是应用层之外的最后一道校验。本节逐条解释 V1__init.sql 里的三张表、主外键、检查约束和部分唯一索引,并用真实插入失败验证每条规则挡住的是什么错误,以及哪些规则不该交给应用层,哪些又必须留给数据库。
SQL 散落在服务层会让事务、错误码和测试都变得难以收口。本节把图书、会员、借阅三类 SQL 收进 JdbcBookRepository、JdbcMemberRepository 与 JdbcLoanRepository,用行映射还原 record,并把 SQLState 翻译成 libraryd 的 ErrorCode。
每条请求都新建物理连接会拖慢 libraryd,也很快耗尽 PostgreSQL 的连接数。本节用 HikariCP 建立有界连接池,讲清 maximumPoolSize、connectionTimeout、maxLifetime 与 leakDetectionThreshold 的含义,并用真实日志定位连接泄漏和池耗尽。
libraryd 从内存仓储走向 PostgreSQL,第一步是把 JDBC 的四个对象用对:DriverManager 与 DataSource 的取舍、PreparedStatement 的参数绑定、ResultSet 的列读取,以及生成键与资源关闭。本节用真实容器跑通图书和借阅记录的写入与查询。
《Java 编程入门》全 61 篇,以 library 图书管理项目为主线,从 JDK 25 工具链、语言基础、面向对象、集合与泛型、Stream、异常与 IO,讲到 JUnit 5 测试、Maven 打包与 REST 服务。本页按六个部分汇总全书章节索引、学习路径与阅读入口。
把测试从「跑过」变成「可验收」:用 JaCoCo 的 LINE 与 BRANCH 计数读懂报告,按模块设置 60% 行覆盖率门槛,在 quality profile 里串起编译、测试、Checkstyle、SpotBugs 和覆盖率,最后落到一条 CI 命令。
区分 Checkstyle 管源码风格、SpotBugs 管字节码缺陷:给出一份只保留命名、导入、大括号和魔法数字的精简规则集,用真实违规输出解释阈值与抑制范围,再用 CT_CONSTRUCTOR_THROW 说明 SpotBugs 的第一条告警为什么把异常类声明为 final 就能修复。
把 javac 的 -Xlint 从编辑器提示变成构建门禁:先看 rawtypes 与 unchecked 的真实告警,再用 maven-compiler-plugin 的 -Xlint:all,-processing 加 -Werror,最后给出 libraryd 里处理泛型与受检异常的写法。