如何使用JPA Criteria API查询实体类Map属性中的指定键值对
问题原因
joinMap、MapJoin这类API仅适用于映射为关联关系(如@ElementCollection、@OneToMany)、存储在独立关联表的Map属性。你的properties字段是被序列化为字符串存储在当前表的nvarchar字段,不属于关联集合,因此无法使用关联查询相关API。
解决方案
由于字段存储的是序列化后的结构化字符串(默认场景为JSON格式),可以通过CriteriaBuilder.function()调用数据库原生的结构化数据提取函数,获取指定键对应的值后做等值匹配,无需修改数据库结构。
以下为不同数据库的实现示例:
1. MySQL 场景
MySQL使用JSON_EXTRACT提取JSON键值,可搭配JSON_UNQUOTE去掉结果的引号:
CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<Item> criteriaQuery = cb.createQuery(Item.class); Root<Item> itemRoot = criteriaQuery.from(Item.class); criteriaQuery.select(itemRoot); // 配置需要匹配的键值对 String key1 = ITEM_PROPERTY_1; String expectValue1 = "123"; String key2 = ITEM_PROPERTY_2; String expectValue2 = "456"; // 构造第一个键值对匹配规则 Predicate pre1 = cb.equal( // 调用JSON_UNQUOTE去掉提取结果前后的引号 cb.function("JSON_UNQUOTE", String.class, cb.function("JSON_EXTRACT", String.class, itemRoot.get("properties"), // 构造JSON路径,键名加双引号避免特殊字符报错 cb.literal("$.\"" + key1 + "\"") ) ), cb.literal(expectValue1) ); // 构造第二个键值对匹配规则 Predicate pre2 = cb.equal( cb.function("JSON_UNQUOTE", String.class, cb.function("JSON_EXTRACT", String.class, itemRoot.get("properties"), cb.literal("$.\"" + key2 + "\"") ) ), cb.literal(expectValue2) ); criteriaQuery.where(cb.and(pre1, pre2)); Item item = em.createQuery(criteriaQuery).getSingleResult();
2. SQL Server 场景
SQL Server直接使用JSON_VALUE即可提取到无引号的键值:
Predicate pre1 = cb.equal( cb.function("JSON_VALUE", String.class, itemRoot.get("properties"), cb.literal("$.\"" + key1 + "\"") ), cb.literal(expectValue1) );
注意事项
- 若你的Map序列化格式为XML等非JSON格式,替换为对应数据库的XML提取函数即可
- 如果需要兼容多类数据库,可以自定义JPA方言函数,将不同数据库的提取逻辑封装为统一函数名调用
- 键名存在特殊字符时需要做好JSON路径转义,避免执行时报语法错误
内容的提问来源于stack exchange,提问作者prix
相关产品推荐
相关产品推荐

