Java EE 企业应用 20:数据库隔离怎样影响审批时看到的数据
审批员第二次查询,可能已经不是同一张清单 采购员提交申请,审批员在同一个审批事务中先查“待批总额”,核对预算后又查一次。两次结果不一致,可能是别人刚提交了一张申请;也可能是应用误把两个查询放在不同事务里。隔离级别描述数据库事务间允许观察到的交错,不保证审批业务不变量自动成立。把“查询第二次没变”当成“没有并发错误”,会遗漏跨行写偏差;把数据库异常统一重试,则可能重复对外下单。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 版本固定为 Java 21/JDBC、Jakarta EE 11、Open Liberty 26.0.0.5、PostgreSQL 16.15。现有 001-initial.sql 有 purchase_request(tenant_id,status,version,total)、明细表、订单表,但没有“每租户同时只能有一张批准申请”的约束。本章不修改 schema 或工程,用独立数据库会话讲清观察边界;下面的双连接实验尚未执行,NOT_RUN。 隔离是数据库行为,...
Java EE 企业应用 19:异常、调用入口与事务边界怎样配合
抛错并不自动回答“哪些行提交了” 采购审批失败时,API 可能返回 400、409 或 500,然而 HTTP 状态不是数据库事务的最终状态。假设服务方法已经把申请改为 APPROVED,随后生成订单失败;如果异常被吞掉、事务仍然正常结束,客户看见错误,数据库却保存了审批。反过来,用户输入校验在进入服务方法前失败,数据库压根没有变化,也谈不上“回滚了两张表”。定位必须同时说明调用入口、异常离开哪个边界、事务管理器如何标记,以及提交后行集合。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 本篇沿用 Java 21、Jakarta EE 11 / Jakarta Transactions 2.0、Open Liberty 26.0.0.5、PostgreSQL 16.15。已有 ProcurementUseCases 是 CDI @ApplicationScoped bean,draft、submit、approve、reject、order 方法标 @Transactional。演示 R...
Java EE 企业应用 10:DTO、JSON 与校验怎样保护业务入口
把客户端报价写入申请,谁替采购员确认过 采购申请的创建请求带商品编码、数量。浏览器也可以附带 unitPrice、total、tenantId,甚至声称“已审批”。如果服务把请求 JSON 直接映射到领域对象并持久化,既会接受伪造金额,也可能绕过状态转换。Web 请求的数据传输对象(DTO)要限制能表达的输入;校验需要保证输入形状;用例则按服务端商品目录重新计算金额,并在可信身份上下文中决定租户。三个关口任意一个缺失,都不能用其它关口的存在来推断安全。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 基线是 Jakarta EE 11、Jakarta JSON Binding(JSON-B)3.0、Jakarta JSON Processing(JSON-P)2.1、Jakarta Validation 3.1、Jakarta REST 4.0 与 Java 21。当前 LabRequestsResource.java 的嵌套 CreateInput/ItemInput 是教学 DTO,...
Java EE 企业应用 18:本地事务与 Jakarta Transactions 谁负责提交
两张表一起写,提交权只能交给一个边界 采购申请要插入 purchase_request 和多条 purchase_line。如果申请落库后明细插入失败,留下的申请既无法计算金额,也不该被审批。try-with-resources 保证连接和语句被关闭,并不保证两次写入构成一个事务。更容易忽略的是:在应用服务器里,拿到同一个 DataSource 的连接,也不能推断代码有权调用它的 commit()。提交权属于事务边界,而不属于“最后一个执行 SQL 的类”。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 本系列的基线是 Java 21、Jakarta EE 11、Open Liberty 26.0.0.5、PostgreSQL 16.15、pgJDBC 42.7.7。累计工程的 examples/javaee-enterprise/application/src/main/java/blog/javaee/application/ProcurementUseCases.java 已用 ...
Java EE 企业应用 09:REST 资源怎样表达采购操作
HTTP 200 可以掩盖一次失败的审批 采购系统接到“提交申请”“批准”“创建订单”三种指令。若三个操作都用 POST /api/do,响应一律 200 并把失败写成正文字符串,浏览器和自动化客户端就难以区分输入错误、单据不存在、状态冲突及真正成功。HTTP 状态不是数据库状态;但映射清楚的方法、资源路径和响应语义,可以让调用者知道何时重试、何时修正输入,何时查询最终结果。当前隔离工程已有 POST 创建、提交、审批、拒绝和下单,没有 GET 按 ID 查询申请的资源方法;本章不能假装“创建、提交、查询”已经三项全齐。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 基线是 Jakarta REST 4.0、Jakarta EE 11、Open Liberty 26.0.0.5、pgJDBC 42.7.7 和 PostgreSQL 16.15。业务数据通过 JDBC 显式 SQL 和 @Transactional 的采购用例访问;先行 JPA 07:本地事务与冲突 使用 RESOURC...
Java EE 企业应用 34:批处理检查点与重启
处理到一半,重启从哪一行开始 供应商每晚送来一份合成目录文件:有正确的 SKU 和价格,也有重复行、非法金额。逐条以 HTTP 提交会把吞吐和错误恢复转嫁给客户端;单事务包住整个文件,则第 900 行的错误可能让前 899 行全部回滚。批处理的关键不是“在后台跑循环”,而是可持久化的输入标识、完成边界和拒绝清单。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 线上采购用的价格目录实现是固定值,采购领域金额约束属于在线订单规则;初始数据库迁移没有供应商目录导入表,服务器配置没有 Batch 作业资源或 job XML。不能拿现有采购场景脚本当批量导入证据。 检查点必须能重现输入 拟议作业以 (supplier_id, file_sha256, import_version) 标识不可变输入,每行有 (file_id, line_no) 唯一键;先将待处理文件复制到隔离、不可变位置,核对行数与摘要后才启动导入。Job 参数记录文件标识和格式版本,而不是客户端任意路径;文件若被替换,旧 ch...
Java EE 企业应用 33:受管执行器与有界轮询
HTTP 返回以后谁唤醒 outbox 第 23 篇的订单与 outbox 已在单库事务中创建,第 30 篇已有内嵌持久队列。当前的受管任务是显式触发一次:HTTP POST /procurement/api/lab/broker/schedule?tenant=... 在取得单在途令牌后将一次 sendNext(tenant,false) 提交给 ManagedExecutorService,立即返回 202。把 new Thread(...) 放到 /order 方法末尾依然错误:线程绕开容器生命周期,内存中待执行任务随部署消失,原订单事务也不会跨线程继续运行。当前实现没有周期唤醒、数据库租约或自动扫描,不能把一次 execute() 写成可持久恢复的定时发布器。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 代码入口是 BrokerLabResource.schedule:通过 @Resource(lookup="java:comp/DefaultManagedExec...
Java EE 企业应用 32:外部 HTTP 超时与未知结果
超时后,供应商可能已经收到了订单 通知消费者向供应商采购接口发送订单。只有能证明请求字节根本未发出、且没有代理代发时,才可断言这次请求未进入对端;连接异常的文字本身不足以证明这一点。写完请求后读取响应超时,更不能判断对端是否已经提交。把超时统一解释成“发送失败,立刻重试”会制造重复采购。同一个客户端异常,在外部系统可能对应未处理、已处理或处理中三种状态。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 现有采购用例与存储端口只处理本地订单;演示 REST 资源是入站接口,不是出站供应商客户端。当前工程的回环 supplier stub 已有两次自检记录,但尚无应用出站 HTTP gateway、PG 回写与供应商送达记录;不能将同步下单场景当成外部通知测试。 预算属于业务操作,不属于某次 socket 拟议出站接口 SupplierGateway.send(orderId, tenantId, idempotencyKey) 的幂等键从稳定业务任务 ID 派生,必须跨发布器重试、消费重投...
Java EE 企业应用 31:消费重投、去重与死信
处理过,不等于已经确认 订单通知任务从 outbox 发到队列后,消费端可能写好本地收据,却在 JMS 提交前回滚;再投递必须被设计进去。当前消息体实际只有十进制 event ID,本地仅写收据,并没有“通知结果”副作用。另一个尚未实现的路径是永远无法解析的旧版本消息,若无限重试便会占住队列。数据库收据、JMS 重投、外部通知真正生效,分别需要证据。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 已实现的接收入口是 BrokerLabResource.receiveOne:只在 JAVAEE_DEMO_MODE=true 的回环实验服务中由 HTTP 显式调用,使用 JMSContext.SESSION_TRANSACTED 同步接收。它依赖 server-messaging.xml 的内嵌队列与 003 收据迁移;普通 server.xml 不含消息配置。当前没有 MDB、无毒消息隔离/死信地址,也没有真实邮件或外部供应商调用。 把重复投递转成一次业务结果 当前消息体只含 notif...
Java EE 企业应用 30:从 Outbox 到真实消息队列
订单提交后,消息到底在哪里 采购单变为 ORDERED 时,通知供应商不能挡住数据库提交。若在同一个方法里先提交订单、再调用消息服务,进程恰好在两步之间退出,订单已经存在,通知却没有任何可恢复的记录;先发消息再提交,则消费者可能看到一张最后回滚的订单。现有工程已经让第 23 篇的订单与 outbox 在单库事务内写入,由本地 stub 增加尝试次数;本篇接上 Open Liberty 26.0.0.5 的内嵌持久消息引擎,通过受控 HTTP 实验入口发送和同步接收。第 33 篇再把一次发送提交给受管执行器。这个教学闭环并没有给真实供应商发邮件,也没有自动运行的 MDB。 本篇源码、迁移、脚本与测试可从固定版本完整归档取得;版本 20261005-local(基于 1b08ada),SHA-256 见源码清单。 从真实代码进入:采购用例的 order 方法、JDBC 存储实现与002 outbox 迁移负责订单与任务;003 收据迁移记录本地消费。消息配置定义内嵌引擎、jms/ProcurementCF 与 jms/ProcurementNotifications;BrokerLa...










