查询可通过 query.bindTables(new QueryTableBindings(tables)) 在服务端显式绑定本次操作的表映射,
JOIN 继续通过 query.join(...) 声明。FROM、JOIN、条件、投影和排序共用该映射,不替换共享 schema;
没有绑定时沿用原查找行为。绑定只保留在查询操作和其 clone 中,不进入 QueryParam.context,
普通 context 赋值不会安装映射或改变 JOIN。绑定不授权访问、不执行 DDL,调用方负责权限及映射生命周期。
- 轻SQL,重java.
- 动态表单: 动态维护表结构,增删改查.
- 参数驱动动态条件, 前端也能透传动态条件,无SQL注入.
- 通用条件可拓展, 不再局限
=,>,like....where("userId","user-in-org",orgId)//查询指定机构下用户的数据 - 真响应式支持, 封装r2dbc. reactor真香.
DatabaseOperator operator = ...;
//DDL
operator.ddl()
.createOrAlter("test_table")
.addColumn().name("id").number(32).primaryKey().comment("ID").commit()
.addColumn().name("name").varchar(128).comment("名称").commit()
.commit()
.sync(); // reactive
//Query
List<Map<String,Object>> dataList= operator.dml().query()
.select("id")
.from("test_table")
.where(dsl->dsl.is("name","张三"))
.fetch(mapList())
.sync(); // reactive建议配合hsweb4使用.
4.2.3 维护 4.2.3-SNAPSHOT,4.2 继续开发后续补丁版本。根 POM 和 core、RDB 的父版本需同步更新;模块间依赖使用 ${project.version}。
自动发布工作流仅监听 4.2 和 4.2.3 的 push,使用分支 POM 中的版本部署到 Maven 仓库;Pull Request 沿用全量测试工作流。切分维护分支时,应先保留当前版本和对应发布触发,再升级开发分支版本。
列 codec 可以将值编码为 NativeSql:DML 写入器通过 SqlFragments.encodeValue(column, value) 编码普通值、保留显式 SQL,再由 AppendableSqlFragments.addValue(value) 统一追加表达式及参数或普通占位符。列参与、默认值、方言语法和更新策略由各写入器负责;批量 INSERT 保留行参数数组复用,Oracle 保留 CLOB 空值处理,MERGE 保留显式 SQL 能力限制。PostgreSQL 原生 JSON/JSONB 将 SQL NULL 编码为 null 表达式,避免 R2DBC 按 String 绑定空值;其他 JSON 类型保留原有空参数绑定,JSON 文档 "null" 仍是非空文本。Java null 在 UPDATE 中仍表示省略,显式清空使用 NullValue;原生 upsert 的 COALESCE 保留旧值语义保持不变,不能替代 UPDATE 的显式清空。
逐行 INSERT(MultiInsertSqlBuilder)按输入列的原始位置读取行值和函数配置,过滤未知或不可插入列不改变其他列的对应关系。函数片段替代该列的原值,不再执行普通值编码或运行时默认值;普通列继续处理缺少的行值、默认值和显式 SQL。回归验证覆盖过滤列、函数位置及短行与表达式的组合,集中运行 INSERT、UPDATE、结果映射和原生 JSON 用例。
查询操作器按列元数据解码后,通过 ColumnWrapperContext.getDecodedColumn() 记录完成解码的列映射;decode(targetColumn) 统一判断实体、嵌套实体和 Record 的解码身份,仅跳过同一列映射,目标 DTO 使用不同列映射时仍执行其 codec。无 codec 且结果未变化时直接复用上下文。自定义结果包装器、Map 和查询列事件继续收到查询层解码后的值。直接 SQL executor 不自动补充元数据转换,带映射的包装器仍自行解码。原四参数上下文构造器保留;包装器派生上下文可使用 DefaultColumnWrapperContext.copyOf(context) 保留列身份和值,修改派生上下文的行实例后应回传给来源上下文。手工构造时通过第五参数透传 getDecodedColumn()。单列 decoder 的直接查询及组合包装行为保持原有规则。
JPA 实体属性从属性描述符、getter 返回类型和 setter 参数类型解析 Java 类型;Object 属性保持 Object,支持只有 getter 或 setter 的属性。通过 @ColumnType(type = ...) 指定且实现 ValueCodec 的自定义数据类型可提供默认编解码器,显式 @JsonCodec 等编解码注解仍按现有规则优先生效。
@ColumnType(exactString = true) 显式要求字符串按大小写、重音及尾空格精确比较,方言负责列级 DDL:PostgreSQL 使用 COLLATE "C",MySQL 8.0+ 使用 utf8mb4_0900_bin,H2 沿用其 VARCHAR 比较。其他方言若未实现则明确拒绝。默认 false 不改变已有列;它是建表 / 迁移元数据,不会在查询中插入 LOWER 或临时 CAST,也不会自动迁移已有列排序规则。
@JsonCodec(includeNulls = true) 为当前列保留显式嵌套 null;配置使用当前 codec 的独立 mapper,不修改共享默认配置。默认 false 保持原行为。JSON 编解码与物理列类型独立,可组合标准 LONGVARCHAR。
@JsonCodec(useBigDecimalForFloats = true) 将无具体 Java 数字类型的 JSON 小数解码为 BigDecimal,避免高精度正文读回时变成 Double;默认 false 保留原解码行为,可与 includeNulls 组合。
枚举位集合的 in$any 判断 (M & Q) <> 0,新增 in$all 判断 (M & Q) = Q(存储集合包含全部查询项),nin 使用对应否定。没有 any/all 的原 in 仍是 (M & Q) = M(存储集合是查询集合的子集)。通用 EnumInFragmentBuilder 同时接受枚举和已编码的 BIGINT mask,不要求业务选项映射到 Java ordinal。
EnumInFragmentBuilder.of/ofNot 统一选择数据库实现,包括 H2 的参数化 BITAND;调用方无需自行判断方言。
默认 UPDATE-then-INSERT 保存器对只有主键、不可更新列或省略值的输入,使用主键与原有 WHERE 条件查询存在性:匹配则作为已有记录完成保存,计入 SaveResult.updated,不发出物理 UPDATE;未匹配则沿用 INSERT 和唯一键冲突行为。Java null 仍表示省略,NullValue 仍表示显式清空。同步与响应式执行一致,不改变普通 UPDATE 的空列保护、原生数据库 upsert 或默认回退的并发保证。
Conditional.group() 将已有条件整体分组,随后可继续追加条件。例如
query.is("a", 1).or("b", 2).group().nest().is("c", 3).end()
表示 (a = 1 OR b = 2) AND (c = 3)。空条件和单个条件不额外增加嵌套层级,
连续分组不会重复包裹;原 nest() / orNest() 仍只追加新分组。
NestConditional.group() 在当前嵌套层提供相同能力,例如
query.nest().is("a", 1).or("b", 2).group().and("c", 3).end()
表示 ((a = 1 OR b = 2) AND c = 3)。仅重组当前层已有子条件,保留外层连接符、
承载条件的字段和值,以及 end() 返回的原父级构造器;支持多层嵌套。
内置 core、同步及响应式 Query/Update/Delete 均支持此能力,分组保留字段、分页及更新内容,
兼容只读条件列表。自定义构造器或代理需显式实现或委托 group();默认实现抛出
UnsupportedOperationException,不会静默忽略分组。调用新方法的下游须同步使用支持该接口的 core/RDB 版本。
hsweb-easy-orm-rdb 的同步 Update/Delete DSL 携带 Repository 的 repository、columnMapping 等默认上下文。每次同步执行使用独立的 EventContext,before 写入的快照可在同次 after/error 中读取,SQL 结果或原异常也进入该上下文。错误通知再次失败时保留原异常,将通知异常附加为 suppressed。复用 DSL 或结果操作器再次执行时不会残留前次快照、结果或错误;未配置异常转换器时保留 JDBC 原异常。
实现入口为 mapping/defaults/DefaultSyncRepository、DefaultSyncDelete、DefaultSyncUpdate、mapping/events/EventResultOperator 和 metadata/TableOrViewMetadata。保留已发布的同步 DSL 构造签名及已有事件分发入口,新增重载;Reactive 事件链保持现有行为,不在上层树服务补发实体事件。
SyncCrudEventContextIntegrationTest 使用 H2 和正式 JDBC 执行器验证普通同步 Update/Delete 的 before/after/error、默认上下文、真实 SQL、监听器失败与调用方写入的事务回滚,以及重复执行隔离。EventResultOperator 还供 DefaultRepository.doInsert 和 doSave 使用,因此同一夹具直接验证单条/批量 insert/save 的真实 SQL、默认 keys 和 before/result/after 上下文。hsweb-framework 的严格树回归另行验收正式实体事件和监听器失败回滚,再重验下游资产维度完整链路。
集中验证入口:mvn -pl hsweb-easy-orm-rdb -am -Dtest=SyncCrudEventContextIntegrationTest,DefaultSyncRepositoryTest,DefaultReactiveRepositoryTest,DefaultQueryUpdateCoverageTest,DefaultSyncQueryTest -Dsurefire.failIfNoSpecifiedTests=false test。旧 CRUD 用例验证查询及操作兼容性,新用例验证 Update/Delete/insert/save 的正式事件代理路径;hsweb-framework 的 SyncTreeCrudRegressionTest、ReactiveTreeCrudCoverageTest 验证 insert/save/delete 的实体事件和事务链路。本修复基于 4.2,坐标为 org.hswebframework:hsweb-easy-orm-rdb:4.2.2-SNAPSHOT;交付时须先合入并发布可消费的 easy-orm 依赖,再发布 hsweb-framework 及下游 components。修复后的上游 snapshot 尚未正式发布,干净 CI 须先发布或从该上游源码正式构建依赖,不能依赖开发机的本地安装结果。
内置 count 函数默认生成 count(列)。查询方可通过 SelectColumn.option("countRows", true) 显式请求行数计数;表元数据确认目标列非空时生成 count(*),此时 countRows 优先于 distinct、arg 等选项,其他情况沿用原有生成逻辑。调用方需确保关联查询不会将目标列扩展为 NULL。自定义 count 函数仍按其自身实现处理该选项;旧版本不识别 countRows 时继续按原有选项生成 SQL。实际性能取决于索引和执行计划。
通过实体表元数据的 addFeature(...) 可按表注册以下可选 SPI,未注册的表保留已有 CRUD 行为及 before 事件触发时机:
ReactiveQueryResultTransformer在普通实体fetch/fetchOne的异步查询准备完成后变换结果流。每次订阅隔离查询参数和准备状态,select_before监听器通过MappingContextKeys.reactiveQueryResultContext取得同次执行上下文,并可使用reactiveResultHolder.before(...)等待异步准备。该私有上下文的queryParam.sorts按原顺序包含参数排序和 DSLorderBy(...)排序,保留函数、指定值及选项;准备阶段可统一校验、消费或改写,原查询不受影响。准备结果存入上下文的attributes,供结果变换使用。count/countLong参与准备但不变换聚合值;fetchOne仍只消费一行且不应用列表分页偏移。ReactiveCrudExecutionInterceptor在 INSERT/SAVE 的重复主键合并及 UPDATE/DELETE 的 SQL 构造前提供原始输入、修改值快照和执行条件。proceed是保留正常 SQL 与生命周期事件的延迟调用,最多调用一次;拦截器可组合本地事务。UPDATE 的context.omitColumns(...)从物理更新中移除已消费的虚拟属性,不修改调用方实体;context.executeVirtualUpdate(mutation)让仅虚拟字段的更新复用同一 prepare/before/after 事件链,等待 mutation 返回真实匹配行数,并避免主表 SQL。原生和虚拟 continuation 每次执行只能订阅其中一个一次,外层 UPDATE 重新订阅会创建新的执行上下文。启用拦截器的 UPDATE 事件通过MappingContextKeys.submittedValues保留移除虚拟字段前的只读提交值快照,instance继续提供原实体引用。INSERT/UPDATE/DELETE 返回Integer,SAVE 返回SaveResult;集合输入是原始有序批次的只读快照,元素仍为调用方对象引用。
实现应组合并返回 Mono/Flux,保留订阅者 Reactor Context、权限条件、取消和错误,不自行 subscribe()。需要在同连接执行附加 SQL 的结果变换器可通过 getBatchSize(context) 返回正值,框架按段完整消费并释放当前 SQL Result 后再调用变换器,避免流式结果背压阻塞同事务后续查询。分段保留原排序并追加主键作为稳定顺序,保持调用方分页范围;每次订阅只准备一次,每段变换使用同一上下文。多段读取的可见性遵循当前事务隔离级别。默认返回 0 保持原单 SQL 和变换行为。聚合、自定义 SQL 和缺少实体标识的投影需使用独立的显式处理入口。CRUD SPI 不自动启动事务或补充权限,原始集合的批量边界也不代表上游 Publisher 自动流式分批;这些策略由注册方实现。
save(Publisher) 的输入流保留调用方 Context;Repository 的日志等 Context 扩展只应用于内部拦截器及原生持久化链,保留调用方已有的其他 key,拦截器也可在此基础上继续扩展。
接口位于 hsweb-easy-orm-rdb 的 mapping 包。ReactiveQueryResultContext 和 ReactiveCrudExecutionContext 均通过 attributes 保存单次执行状态;不要将请求状态保存在共享 Feature 实例中。
ReactiveRepository.isResultCacheable() 声明完整实体结果能否跨订阅进入仅按实体身份索引的共享缓存,默认 true。默认仓库委托已注册的 ReactiveQueryResultTransformer.isResultCacheable(),未注册时保留 true。转换器默认 false;仅当转换不依赖订阅者上下文,且其依赖数据更新能由既有缓存失效机制覆盖时,才可显式返回 true。缓存命中会跳过查询准备和转换;施加订阅者权限的仓库装饰器仍应返回 false,不能因目标仓库可缓存而放行。上层缓存服务负责缓存策略,不识别业务类型;该声明不控制数据库缓存或调用方显式划分范围的缓存。GenericReactiveCacheSupportCrudServiceTest 在下游 framework 验证纯转换的缓存命中、写后失效和上下文转换绕过缓存。
operator.dml().upsert(table).nativeConflict(true) 显式请求 MySQL 的多行 INSERT ... ON DUPLICATE KEY UPDATE。默认 false 保留额外唯一索引场景下的保守回退。MySQL 会响应任一唯一键冲突,因此仅在所有唯一键冲突都应更新同一业务记录时启用;此选项不提供 PostgreSQL 式的冲突目标选择。显式原生模式不支持 UPDATE WHERE 条件,存在条件时直接报错。更新列、主键保留及传入 null 时的已有处理语义保持不变。