当前位置: 首页 > news >正文

基于Java对于PostgreSQL多层嵌套JSON 字段判重

场景:把复杂的 CommonCondition 条件树以 JSON 形式存入 PostgreSQL,并要求:

  • 子条件顺序、

  • valueList 重复值、

  • 展示用字段(如别名)
    不影响“相同业务逻辑”的判定。
    本文给出一条 Java → PostgreSQL 的端到端可复制方案。


1. 数据库层设计(PostgreSQL)

CREATE TABLE report_condition (id        bigserial PRIMARY KEY,condition jsonb        NOT NULL,signature char(64)     NOT NULL,   -- SHA-256 长度created_at timestamptz DEFAULT now()
);-- 查询时直接按 signature 去重
CREATE UNIQUE INDEX uk_signature ON report_condition(signature);

触发器可选:如果希望完全由 DB 计算签名,可用 plpython3u 调用 Python 归一化脚本;
下文演示 Java 端计算签名后写入,逻辑更清晰。


2. Java 端核心实现

2.1 枚举:白名单字段规则

package com.example.condition;import java.util.List;/** 不同业务场景下保留的字段集合 */
public enum ConditionNormalizeRule {DEFAULT(List.of("table","field","fieldType","type","logicalOperator","operator","valueList","commonConditions")),REPORT (List.of("table","field","type","operator","valueList","commonConditions")), // 去掉 fieldTypeAUDIT  (List.of("table","field","fieldType","type","operator","valueList","commonConditions"));private final List<String> whiteList;ConditionNormalizeRule(List<String> whiteList) { this.whiteList = whiteList; }public List<String> getWhiteList() { return whiteList; }
}

2.2 工具类 ConditionHash

package com.example.condition;import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.*;
import org.apache.commons.codec.digest.DigestUtils;import java.util.*;
import java.util.stream.Collectors;public final class ConditionHash {/** 全局 ObjectMapper,线程安全 */private static final ObjectMapper MAPPER = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL) // 忽略 null.configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true); // 键排序/*** 计算业务等价哈希* @param root  待计算的 CommonCondition 树* @param rule  决定保留哪些字段的枚举* @return      SHA-256 十六进制字符串(64 位)*/public static String sha256(CommonCondition root, ConditionNormalizeRule rule) {try {JsonNode tree = normalize(MAPPER.valueToTree(root), rule);byte[] bytes  = MAPPER.writeValueAsBytes(tree);return DigestUtils.sha256Hex(bytes);} catch (Exception e) {throw new IllegalStateException("compute hash failed", e);}}/* ---------- 私有归一化逻辑 ---------- *//** 递归归一化:保留白名单字段 + 去重排序 */private static JsonNode normalize(JsonNode node, ConditionNormalizeRule rule) {if (node == null || !node.isObject()) return node;ObjectNode obj = MAPPER.createObjectNode();List<String> white = rule.getWhiteList();white.forEach(key -> {if (node.has(key)) {JsonNode val = node.get(key);switch (key) {case "valueList":obj.set(key, normalizeValueList(val));         // 去重+排序break;case "commonConditions":obj.set(key, normalizeChildren(val, rule));    // 递归+排序break;default:obj.set(key, val);                             // 直接保留}}});return obj;}/** valueList 去重+字典序排序 */private static JsonNode normalizeValueList(JsonNode listNode) {if (listNode == null || !listNode.isArray()) return MAPPER.createArrayNode();List<String> list = MAPPER.convertValue(listNode, new TypeReference<>() {});list = list.stream().distinct().sorted().collect(Collectors.toList());return MAPPER.valueToTree(list);}/** commonConditions 递归归一化后按字符串排序 */private static JsonNode normalizeChildren(JsonNode childrenNode, ConditionNormalizeRule rule) {if (childrenNode == null || !childrenNode.isArray()) return MAPPER.createArrayNode();List<CommonCondition> children = MAPPER.convertValue(childrenNode, new TypeReference<>() {});List<JsonNode> sorted = children.stream().map(c -> normalize(MAPPER.valueToTree(c), rule)).sorted(Comparator.comparing(JsonNode::toString)) // 稳定排序.collect(Collectors.toList());return MAPPER.valueToTree(sorted);}private ConditionHash() {}
}

3. 使用示例

3.1 构造两个“业务等价”的对象

CommonCondition condA = CommonCondition.builder().type("logical").logicalOperator("AND").commonConditions(List.of(CommonCondition.builder().type("base").table("user").field("age").operator("GT").valueList(List.of("18", "18", "20"))  // 重复值.build(),CommonCondition.builder().type("base").table("user").field("status").operator("IN").valueList(List.of("ACTIVE", "LOCKED")).build())).build();/* 把子条件顺序颠倒,再加一个别名字段,但业务含义不变 */
CommonCondition condB = CommonCondition.builder().type("logical").logicalOperator("AND").tableNameAlias("u")            // 展示用,不参与哈希.commonConditions(List.of(CommonCondition.builder().type("base").table("user").field("status").operator("IN").valueList(List.of("LOCKED", "ACTIVE", "ACTIVE")) // 重复+乱序.build(),CommonCondition.builder().type("base").table("user").field("age").operator("GT").valueList(List.of("20", "18"))   // 乱序.build())).build();

3.2 计算哈希并判重

String sigA = ConditionHash.sha256(condA, ConditionNormalizeRule.DEFAULT);
String sigB = ConditionHash.sha256(condB, ConditionNormalizeRule.DEFAULT);System.out.println("sigA = " + sigA);
System.out.println("sigB = " + sigB);
System.out.println("same = " + sigA.equals(sigB));   // true

3.3 插入 PostgreSQL

String sql = """INSERT INTO report_condition(condition, signature)VALUES (?::jsonb, ?)ON CONFLICT (signature) DO NOTHING""";
try (PreparedStatement ps = conn.prepareStatement(sql)) {ps.setString(1, new ObjectMapper().writeValueAsString(condA));ps.setString(2, sigA);ps.executeUpdate();
}

ON CONFLICT (signature) DO NOTHING 利用唯一索引实现幂等写入,天然判重。


4. 性能 & 扩展

子条件规模耗时(MacBook M2)建议
< 100< 1 ms无需优化
100~1 0001~8 ms缓存哈希
> 1 000> 10 ms并行流 + 缓存

缓存示例(Lombok):

@Getter(lazy = true)
private final String hash = ConditionHash.sha256(this, ConditionNormalizeRule.DEFAULT);

5. 小结

  1. 数据库:JSONB + 唯一索引 signature,一行 SQL 完成判重。

  2. JavaConditionHash.sha256(root, rule) 统一出口,顺序、重复、别名全部抹平。

  3. 枚举:新增业务场景只需再加一个 ConditionNormalizeRule 值,零侵入。

http://www.dtcms.com/a/306027.html

相关文章:

  • 18.编译优化
  • SQL167 连续签到领金币
  • MySQL 9 Group Replication维护
  • 达梦数据库(DM Database)角色管理详解|了解DM预定义的各种角色,掌握角色创建、角色的分配和回收
  • C++:STL中list的使用和模拟实现
  • Keepalived 实战
  • 《C++二叉搜索树原理剖析:从原理到高效实现教学》
  • 如何利用 Redis 的原子操作(INCR, DECR)实现分布式计数器?
  • Java 控制台用户登录系统(支持角色权限与自定义异常处理)
  • 生成模型实战 | GLOW详解与实现
  • 从理论到实践:全面解析机器学习与 scikit-learn 工具
  • 汽车、航空航天、适用工业虚拟装配解决方案
  • 关于“PromptPilot” 之4 -目标系统软件架构: AI操作系统设计
  • 第六章:进入Redis的List核心
  • 【8月优质EI会议合集|高录用|EI检索稳定】计算机、光学、通信技术、电子、建模、数学、通信工程...
  • 人工智能与家庭:智能家居的便捷与隐患
  • 移动端WebView调试实战 全面排查渲染性能与布局跳动问题
  • ISO 26262 汽车功能安全(腾讯混元)
  • MongoDB系列教程-第二章:MongoDB数据库概念和特点、数据库操作、集合操作、文档操作、规范及常见问题解决、实际应用示例
  • JXD进步25.7.30
  • Thales靶机
  • 《Vuejs设计与实现》第 12 章(组件实现原理 下)
  • 非凸科技受邀出席第九届AIFOF投资创新发展论坛
  • 前端安全防护:XSS、CSRF与SQL注入漏洞深度解析与防御
  • 亚马逊云科技:赋能企业数字化转型,解决实际发展难题
  • 【Axure高保真原型】轮播条形图
  • 让科技之光,温暖银龄岁月——智绅科技“智慧养老进社区”星城国际站温情纪实
  • 【HarmonyOS】鸿蒙应用HTTPDNS 服务集成详解
  • 【Lua】元表常用属性
  • 【MySQL】MySQL索引—B树/B+树