深入 Hibernate 05:persist 返回时究竟完成了什么
有了订单 id,订单是否已经成立 订单接口创建金额为 12.34 的 PurchaseOrder,调用 persist 后立即取得主键。随后库存检查失败,事务回滚。调用方若只判断 id != null 就发送“下单成功”,会把一次未提交的写入当成业务事实。这个错误在序列和数据库自增主键下都可能发生,只是插入语句出现的时点不同。 本章要求的不变量是:只有提交成功、另一条 READ COMMITTED 连接可见订单行,才把持久化订单视为成立。Java 对象是否托管、是否取得主键、动作是否入队、数据库是否已经执行 INSERT,要分别记录。单表模型沿用第 03 篇,只增加测试专用的 IDENTITY 对照实体,不提前修改共享订单模型。 研究对象固定为 Hibernate ORM 7.1.36.Final、Jakarta Persistence 3.2、Java 21,源码 SHA 为 ca7715d7c1afbd46d518752115848bffd9322413。PostgreSQL 序列模型使用 allocationSize = 1;IDENTITY 模型只有主键和业务单号,没有关...
深入 Hibernate 04:Java 值怎样绑定到列
金额已经传入,为什么读回来变了 订单金额是 new BigDecimal("12.345"),数据库列是 numeric(19,2)。Java 对象可以保留三位小数,数据库列只能保留两位。调用 persist 的参数类型正确,不代表这两个数值域相同;写入成功也不代表重新查询能得到逐位相同的数。 这个问题包含三次转换:Java 值转换为 JDBC 可绑定的值,驱动把值交给服务器,服务器按实际列定义接受或拒绝它。注解描述映射,不是每次 setter 执行的校验器。@Column(scale = 2) 不会自动把 Java 字段改成两位小数,validate 也不能代替业务金额规则。 本篇固定 Hibernate ORM 7.1.36.Final 与 JDK 21。完整实验是仓库中的 examples/hibernate-lab/src/test/java/blog/hibernate/Chapter04Test.java,不需要从文中拼接代码。当前检出版本在专用 PostgreSQL 16.15 上三项测试通过;原始输出、JUnit 报告与另起连接的终态见 exa...
深入 Hibernate 03:托管、脱管和删除怎样影响订单
实验材料状态:下文保留先前 PostgreSQL 16.15 的原始记录。当前检出版本在专用 PostgreSQL 16.15 上复跑 00–03,6 项测试通过;新运行的报告在 examples/hibernate-lab/evidence/00-03/20261003T030855Z-pg16-cumulative/。主仓另保留 2026-10-02 的 PostgreSQL 17.6 原始运行;2026-10-03 同步后在 PostgreSQL 17.6 复跑 00–12,50 项通过,见 examples/hibernate-lab/evidence/00-12/20261003-pg17-synced-local/。两种环境的运行分别留证。 修改了对象,为什么订单金额没有变化 一个 PurchaseOrder 从数据库加载后,在业务方法里被 detach,随后金额字段从 5.00 改成 9.99。Java 对象显示 9.99,再次从同一数据库行加载却仍是 5.00。原因不是写入失败,而是这份 Java 对象已脱离持久化上下文,后续字段变化没有自动成为本次工作单元的...
深入 Hibernate 02:注解映射在启动时接受什么检查
实验材料状态:下文保留先前 PostgreSQL 16.15 的原始记录。当前检出版本在专用 PostgreSQL 16.15 上复跑 00–03,6 项测试通过;新运行的报告在 examples/hibernate-lab/evidence/00-03/20261003T030855Z-pg16-cumulative/。主仓另保留 2026-10-02 的 PostgreSQL 17.6 原始运行;2026-10-03 同步后在 PostgreSQL 17.6 复跑 00–12,50 项通过,见 examples/hibernate-lab/evidence/00-12/20261003-pg17-synced-local/。两种环境的运行分别留证。 错列名不该等第一笔订单暴露 创建订单接口引用了一个叫 does_not_exist 的数据库列,而真实 purchase_order 表没有该列。若应用启动只登记了 Java 注解却从未核查数据库,错误会延迟到写入或查询时才显露;订单服务的第一笔请求便充当了上线校验。第 00 篇建立的单表模型和第 01 篇的对象身份实验仍然适...
深入 Hibernate 01:同一数据库行对应几个 Java 对象
实验材料状态:下文保留先前 PostgreSQL 16.15 的原始记录。当前检出版本在专用 PostgreSQL 16.15 上复跑 00–03,6 项测试通过;新运行的报告在 examples/hibernate-lab/evidence/00-03/20261003T030855Z-pg16-cumulative/。主仓另保留 2026-10-02 的 PostgreSQL 17.6 原始运行;2026-10-03 同步后在 PostgreSQL 17.6 复跑 00–12,50 项通过,见 examples/hibernate-lab/evidence/00-12/20261003-pg17-synced-local/。两种环境的运行分别留证。 一行不是一个全局 Java 对象 订单服务按数据库主键查询 PurchaseOrder 两次,两次返回值是否满足 ==?只有在说明查询发生在哪个持久化上下文、上下文是否被清空之后,这个问题才有答案。同一张订单在不同请求中也可能同时以两个 Java 对象表示,不能据此推断数据库有两行,或从 Java 引用相同推断另一个事务的可见...
深入 Hibernate 00:从对象操作到数据库结果,证据链怎样建立
实验材料状态:下文保留先前 PostgreSQL 16.15 的原始记录。当前检出版本在专用 PostgreSQL 16.15 上复跑 00–03,6 项测试通过;新运行的报告在 examples/hibernate-lab/evidence/00-03/20261003T030855Z-pg16-cumulative/。2026-10-02 的 PostgreSQL 17.6 基线位于 examples/hibernate-lab/evidence/local-baseline/20261002-pg17/;同步后复跑 00–12,50 项通过,见 examples/hibernate-lab/evidence/00-12/20261003-pg17-synced-local/。系列主模块最终累计 136 项通过,见 examples/hibernate-lab/evidence/cumulative/20261003-pg17-final/。各环境与各轮运行分别留证。 问题是哪个观察者看到了订单 创建订单的代码执行到 persist(order),此时订单对象可能已经有 ...
系统设计 E05:从票务架构到可执行状态机
高层设计写着“座位最多卖一次”,类图里有 Seat、Order、Payment,并不能证明代码守住了这个约束。真正需要落地的是谁拥有状态、哪条操作能改变它、检查与更新是否处在同一原子边界,以及失败后返回什么可执行结果。 本篇把系列票务场景缩小为单座位占用与支付确认,使用 Python 标准库和实际 SQLite 数据库验证状态机。没有真实支付渠道,没有分布式锁,也不把这个小程序称为完整票务系统。目的是把架构层的数据保证变成模块契约与数据库操作,避免为名词各建一个类后仍然允许非法状态跳转。 对象边界由不变量决定 同一座位至多被一个有效持有人占用,已售座位不能因旧占座或迟到回调重新转给别人;同一支付 ID 重复通知不重复记录,同 ID 换持有人要拒绝。这里的“支付确认”是本地假支付事件,若不能完成销售,返回 refund_required 表示需要外部补偿,不表示已经退款。 状态只有 FREE、HELD、SOLD。占座从 FREE 进入 HELD,或在 HELD 到期后由新持有人重新占用;付款只能在当前 HELD 尚未到期、持有人匹配时进入 SOLD。SOLD 在本实验没有反向边,...
系统设计 E04:流式模型网关的取消与用量边界
流式接口发出第一段内容之后,失败重试就不再只是重新请求一次。用户可能已经看到半句,模型端可能已经产生全部用量,网关却只记录了几个片段。取消、计费与重试必须共享同一个请求状态,而不能由三个互不相干的中间件分别处理。 本篇设计多租户 LLM 服务网关,范围包含流式转发、有限并发、准入配额、显式取消和用量核对;排除真实模型推理、供应商账单、工具调用与训练。实验启动真实回环 HTTP 服务和客户端,但“模型”仅按序生成数字片段,每段不等于真实 tokenizer 的 token,结果不能用来估算任何模型价格或 GPU 吞吐。 把首段、完成和取消分别建模 业务不变量是一个请求不能同时占用多个有效执行槽,取消后其本地执行应释放资源,已发送部分输出的请求不能被透明重试成另一段独立回答。目标拟定为准入后首 token p99 两秒以内、显式取消在一秒内停止本地任务;完整回答延迟取决于输出长度,不能拿首 token 指标代替。 接口候选为 POST /generations,携带租户身份、请求 ID、输入与最大输出预算,返回流;POST /generations/{id}/c...
系统设计 E03:指标日志平台的基数与保留代价
指标平台常在查询量还不大时就被标签组合拖垮。一个请求计数器加上 user_id,不是多存一个字符串,而是可能把六十条时间序列变成六万条。保留期和降采样又会改变哪些历史问题还能回答,平台设计不能只报“每天写入多少 GB”。 本篇限定为内部服务指标与结构化日志,支持服务健康查询、地域汇总和单请求排查。排除完整 APM、全文搜索产品复刻与真实用户数据。写入确认只承诺进入约定持久层,查询需返回数据时间范围及缺失信息;已过期原始数据不得用降采样结果冒充原始精度。 先固定查询集,再选数据布局 三类查询决定三条访问路径:查询某服务最近一分钟最大延迟,需要细粒度窗口值;查询各地域总请求量,需要按有限标签聚合;查询 request_id 的错误日志,需要可定位明细。把三类数据都放同一套标签系统,会让单请求排查所需高基数标识污染常驻指标。 指标模型为 metric_name, labels, timestamp, value,标签集合标识一条序列;日志模型为 timestamp, service, level, request_id, body。写接口 POST /metrics/batch、PO...
系统设计 E02:事件归因的去重迟到与修正
同一笔转化被上报两次,是重复事件问题;点击在转化之后才到达,是迟到数据问题;转化原先归给广告甲、补齐数据后改归广告乙,是归因修正问题。把三者都交给“消息去重”处理,会让统计稳定地算错。 本篇只使用合成曝光、点击与转化,构建一个固定窗口的最后点击归因模型。没有真实广告用户数据,没有接入投放平台,也不计算应付账款。教学不变量是同一转化 ID 只进入一次业务总额、修正保持该笔总额守恒、窗口外点击不得获得归因。归因报表与资金账务的边界必须在模型之前确定。 事件时间、到达时间与业务时间不能混用 事件采用 event_id, kind, user_key, campaign_id, event_time, ingest_time;转化另带 conversion_id, amount_minor, currency。金额使用最小货币单位整数,本例 500 表示五百单位的教学金额,不暗示任何真实币种。身份关联键采用合成 u1,不讨论跨设备身份识别;若身份关联不可靠,归因本身应允许未知结果。 接入接口 POST /events 返回“已接受并持久保存”,不承诺归因结果立即最终化。查询 GET /...









