Java EE 企业应用 39:一个企业应用怎样完成交付验收
交付结论必须能指回业务终态 采购人提交两支纸和三支笔,审批人批准后建单,重复请求只取得同一订单;同时数据库写入失败、通知未送达、租户冒用与发布回切都不能制造第二张订单。这样的交付目标不是一次 /health 的 200,也不是一张“所有测试通过”的截图。它要求每条需求都能对应入口、输入、失败注入、独立可读的终态和原始记录;未实现的能力应留空,不能靠旁边的绿色用例补齐。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 教学基线在 examples/javaee-enterprise/README.md:JDK 21、Jakarta EE 11 Platform、Open Liberty 26.0.0.5、PostgreSQL 16.15、pgJDBC 42.7.7。工程已有隔离 WAR、CDI/受管事务、JDBC 与 SQL、教学采购接口和部分原始验证记录。JAVAEE_DEMO_MODE=true 通过不可信 X-Lab-Tenant 开放本机实验入口,不提供正式认证;目前仅有 Libe...
Java EE 企业应用 38:发布与回退怎样覆盖应用、配置和数据库
旧 WAR 回去了,旧数据能跟着回去吗 采购申请从 SUBMITTED 进入 APPROVED 时,业务数据已经改变;更换 WAR 不是数据库的撤销操作。一次可回退发布必须明确三个版本:应用包、运行配置和 schema;还要问旧代码能否读取新代码写入的数据。在有异步通知的版本里,尚有在途任务和消息的协议版本。只存一份旧 WAR,不能据此保证业务回退。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 基线工程 examples/javaee-enterprise/README.md 提供单 WAR、deploy/server.xml 和只针对空库的 db/migrations/001-initial.sql。版本为 JDK 21、Jakarta EE 11 Platform、Open Liberty 26.0.0.5、PostgreSQL 16.15;数据库口令从环境注入。现有 SQL 是初始化脚本,不是从旧 schema 升级的可重入迁移;001-initial.sql 再运行会因已有表...
Java EE 企业应用 37:吞吐、尾延迟与资源预算怎样共同测量
健康接口每秒能回多少次,不是采购容量 采购操作要获取连接、锁定业务状态、写入申请与订单,还可能等待外部确认。对一个只返回 ready 的 HTTP 接口压测,得到的数值主要反映 Web 入口;即使吞吐很高,也不能推导“同时批准一批申请仍满足唯一订单约束”。容量问题要求负载、系统状态与业务终态同时固定。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 基线工程在 examples/javaee-enterprise/README.md:JDK 21、Jakarta EE 11 Platform、Open Liberty 26.0.0.5、PostgreSQL 16.15、pgJDBC 42.7.7。JAVAEE_DEMO_MODE=true 的实验接口仅能在 loopback 隔离运行。尚无压测报告、生产流量比例、连接池瓶颈或 p99 数字;以下微型采样只是说明怎样得到原始时延,不是吞吐/性能验收。 现有采购链路比健康检查复杂:FixedPriceCatalog 把两支纸与三支笔算成 2 ...
Java EE 企业应用 36:一次采购操作怎样关联日志、指标与追踪
请求返回了 500,申请到底写进去了吗 供应商的采购申请从 HTTP 到 SQL,再到通知消费者;同一个业务动作可能跨越多个请求和事务。“见到异常日志”不等于“写入失败”,“看到 trace”也不等于“通知送达”。观测首先要能回答可核查的问题:这次请求的 ID 是什么、哪一个申请受影响、事务提交后数据库是什么、消息处理是否留下了独立结果。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 现有工程 examples/javaee-enterprise/ 的 RequestIdFilter 为 /api/* 请求生成 UUID,放入 request attribute 和 X-Request-ID 响应头,在调用结束时清理 attribute。它没有把这个 ID 写进结构化日志、SQL span 或异步消息;请求 ID 只是入口证据,不能声称已有端到端追踪。JDK 21、Jakarta EE 11、Open Liberty 26.0.0.5、PostgreSQL 16.15 是当前隔离基线。...
Java EE 企业应用 35:哪些测试必须运行在真实数据库和容器里
一条采购链路,需要几种不同的证据 采购申请包含两支纸和三支笔,服务端核价后金额为 13.75;提交、审批、建单,重复建单仍返回原订单。这个业务结果可以在纯 Java 对象中推演,却不能用模拟仓储证明数据库真的拒绝重复订单,也不能用单独的 JDBC 测试证明 WAR 在容器中拿到了预期数据源。测试分层不是为了多跑几套测试,而是为了在失败时知道哪个边界已经被覆盖。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 入口是 examples/javaee-enterprise/README.md。当前累计工程有 domain/ 的规则测试、PostgreSQL 16.15 上的显式 SQL、Open Liberty 26.0.0.5 的受管 DataSource 和隔离教学接口;它不是完整认证、消息或发布工程。JDK 21、Jakarta EE 11 Platform、pgJDBC 42.7.7 与 Maven Wrapper 3.9.9 是本地基线。先核对服务实际连接的 javaee_lab,...
JPA 持久化 06:抓取、N+1 与缓存不能混为一谈
审批列表的查询很短,SQL 为什么可能很多 一页十条采购申请,界面要求在每条下方展开明细。先查询十条 ProcurementRequest,再循环调用 request.lines(),代码看起来只有一条显式查询,却可能在遍历时为每个尚未加载的集合再访问一次数据库。N+1 不是 JPQL 的语法错误,而是“先取 N 个根对象,再逐个访问未取到的关联”造成的访问路径问题。同一段代码在一级缓存已有对象、二级缓存命中或 provider 改用批量抓取时,SQL 条数都可能变化;1+N 是便于定位问题的模型,不是运行结果或规范保证。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 1b08ada,SHA-256 见源码清单。 本篇接续 04 篇的关系映射和 05 篇的排序分页。累计工程 examples/jpa/ 中,ProcurementRequest.lines 是 @OneToMany(mappedBy = "request", fetch = LAZY),ProcurementLine.request 为 @ManyToOne(fetch = LAZY...
JPA 持久化 05:JPQL、Criteria 与原生查询的边界
查询返回了对的行,为什么审批仍可能出错 采购审批列表通常只要申请编号、状态与金额;详情页才需要实体与明细。把两者都写成“查出申请实体,再遍历关联”会让列表承担不必要的对象加载,而在同一个工作单元里执行批量驳回,又可能让已经加载的申请仍显示为待审批。问题并非查询语言的选择,而是查询的结果形状、排序条件、持久化上下文与数据库行是否被当成了同一件事。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 1b08ada,SHA-256 见源码清单。 本篇只讨论 Jakarta Persistence 3.2 的查询契约,先修实体与关联见 01–04 篇。案例沿用 ProcurementRequest(申请)、ProcurementLine(明细)、ProcurementOrder(订单):租户字段为 tenantId,申请状态从 DRAFT、SUBMITTED 走向 APPROVED 或 REJECTED,批准后才允许建单。查询代码负责选出候选申请,不能替代服务层的租户授权、状态校验和数据库的一申请一单约束。已有 ProcurementJpaTest 覆盖了小规模查询与 bulk ...
JPA 持久化 07:本地事务、JTA 与冲突
审批成功,订单却可能失败 采购申请从 SUBMITTED 变成 APPROVED,随后才能创建订单;订单表对 request_id 有唯一约束。假如审批状态已经提交,创建订单时却发现另一个请求抢先插入,应用不能再声称“审批和建单是一次原子操作”。反过来,把所有步骤放入一个事务也不能保证两个审批员不会同时读到旧状态。事务边界、并发控制和数据库约束各解决一件事:前者决定哪些写入一起提交,版本或锁控制相互覆盖,唯一键守住“每申请至多一单”。三者都不能代替租户鉴权和重复请求的业务语义。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 1b08ada,SHA-256 见源码清单。 本系列以 Jakarta Persistence 3.2、Java 21、Hibernate ORM 7.1.36.Final 和 PostgreSQL 16 为讨论环境。已有累计工程 examples/jpa/ 采用 RESOURCE_LOCAL,不是 JTA 容器。归档的 PostgreSQL 16.15 回归有六项测试通过,其中两项直接覆盖本章的 flush 回滚和乐观锁交错;悲观锁双会话和真实 ...
Java EE 企业应用 07:Filter 与 Listener 怎样参与请求生命周期
请求 ID 写上去了,为什么还可能丢失清理 采购申请查询进入 Web 应用之前,希望给每次调用分配一个请求 ID;无论后续成功、业务异常还是内部派发,都不应把本次的跟踪数据带进下一个请求。常见实现是在过滤器中设置 ID,然后调用 chain.doFilter(),最后移除。这里有两个容易混同的“之后”:调用链返回过滤器的时刻,与响应已写完且所有异步任务都结束的时刻。同步请求通常可以以 finally 清理本线程工作;异步请求的真正完成时点则需要额外机制。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 现有工程已有 RequestIdFilter.java 和 StartupListener.java。前者覆盖 /servlet/*、/api/* 的 REQUEST、FORWARD、ASYNC 派发,后者在上下文初始化时保存启动时刻并在销毁时移除;源码存在不等于异常/异步派发已实际演练,也不等于这个请求 ID 可在服务器重启后恢复。 过滤的是一次派发,不一定是一次客户端请求 Jakart...
JPA 持久化 04:继承、关联与所有权
把明细放进集合,为什么外键仍可能没有变化 采购申请有两条明细,审批后只允许生成一张订单。把 ProcurementLine 加入 ProcurementRequest.lines,Java 内存里的集合已经变了;但如果明细的 request 仍指向旧申请,数据库该依据哪一侧更新外键?再设想“批准”和“驳回”是两种审批记录:查询共同父类能否返回两种子类?把这些问题都叫作“对象关系”会遮住两条不同的线:继承是同一身份体系下的多态映射,关联是不同实体之间关系的所有权。上章讨论过金额嵌入对象无独立身份,这里恰好要判断什么时候需要独立实体。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 1b08ada,SHA-256 见源码清单。 业务范围不变:DRAFT → SUBMITTED → APPROVED → ORDERED,或 SUBMITTED → REJECTED;只有 APPROVED 能下单,每申请最多一单。每条明细归一张申请,订单记录对应申请,租户不可越权。本文以前三章的实体身份、flush 与回滚、类型和主键为先修;代码入口只针对将来的独立 examples/jpa/...










