根据标签,我假设您使用querydsl-jpa 创建 JPQL(或 Hibernates HQL)查询。需要注意这一点,因为为了让 JSON 类型与 Querydsl 一起使用,它们不仅必须与 Querydsl 和 MySQL 一起使用,而且最重要的是与查询语言本身一起使用,在这种情况下来自 Hibernate。
我假设您已经设法将您的 json 列正确映射到 Jacksons JsonNode 类型。如果不是,那么需要注意的是,对于任何非标准类型,自定义类型都需要在 Hibernate 中注册。您可以制作自己的自定义类型,也可以使用现有的实现。我强烈推荐由 Vlad 开发的 hibernate-types 库(包括我自己在内的其他一些人的贡献)。如何将hibernate-types 集成到您的项目中在他对这个 Stackoverflow 问题的回答中得到了很好的解释:https://stackoverflow.com/a/37946530/2104280
你需要做的第一件事是在你的项目pom.xml配置文件中设置如下Hibernate TypesMaven依赖:
<dependency>
<groupId>com.vladmihalcea</groupId>
<artifactId>hibernate-types-52</artifactId>
<version>${hibernate-types.version}</version>
</dependency>
现在,您需要在类级别或 package-info.java 包级别描述符中声明 JsonType,如下所示:
@TypeDef(name = "json", typeClass = JsonType.class)
而且,实体映射将如下所示:
@Type(type = "json")
@Column(columnDefinition = "jsonb")
private Location location;
现在您将能够在查询中的简单操作中使用 JSON 属性。允许的操作基本上包括基本比较(=、!= 和IS (NOT) NULL)。其他操作不可用,因为这些仅在 MySQL 中定义,而不是在 JPQL 或 HQL 查询语言中。
您想检查 JSON 数组是否包含特定元素。我假设 category_ids 代表一个 JSON 数组。那么这意味着您想要生成的 MySQL sn-p 可能类似于category_ids ? :categoryId。
我想指出的是,这仅适用于存储为字符串的 ID,而不是 JSON 数组中的数字。您可能希望将category_ids 映射为bigint[]。与 JSON 类型一样,Hibernate-Types 库也支持数组类型,因此答案对数组同样适用。
但是,JSON_CONTAINS(category_ids, :categoryId) 不是有效的 HQL/JPQL,因为该查询语言中不存在该函数。如果我们想在 HQL/JPQL 中渲染 JSON_CONTAINS(category_ids, :categoryId),我们需要定义一个自定义函数,将 JSON_CONTAINS(category_ids, :categoryId) 渲染为 SQL。
在 Hibernate 中声明自定义函数有两种方式。
- 扩展您正在使用的方言并在那里声明函数。这种方法在https://stackoverflow.com/a/42486291/2104280 中有很好的描述。
- 在引导期间使用
MetadataBuilderInitializer SPI 注册自定义函数。这种方法在https://stackoverflow.com/a/41369853/2104280 中有所描述。
使用MetadataBuilderInitializer 方法,代码最终将如下所示:
public class JSONMetadataBuilderInitializer
implements MetadataBuilderInitializer {
@Override
public void contribute(MetadataBuilder metadataBuilder,
StandardServiceRegistry serviceRegistry) {
metadataBuilder.applySqlFunction("json_contains_key",
new SQLFunctionTemplate(BooleanType.INSTANCE,
"JSON_CONTAINS(?1, ?2, '$'"));
}
}
最后,您的JsonNode 属性得到了一个Hibernate 类型,以及一个用于检查键是否存在的自定义函数。在 JPQL/HQL 中可以按如下方式使用密钥:
SELECT product FROM Product product WHERE json_contains_key(product.categories, '1234') = true;
这将产生以下 SQL:
SELECT * FROM product WHERE JSON_CONTAINS(categories, '1234', '$') = true;
但是,您想通过 Querydsl 构造这个 JPQL/HQL 查询,这意味着还需要几个步骤。我们需要为创建的函数构造新的表达式类型。基本上有两种选择:
- 创建一个新的
Operator 实现,并在扩展的JPQLTemplates 中为该Operator 注册一个Template。
- 创建一个
TemplateExpression(字面意思是JPQL 的一个片段)。
第二种方法更简单,我将在这里使用。使用TemplateExpression,您最终会在 Querydsl 中使用以下代码来生成上述 JPQL 查询:
queryFactory.select(QProduct.product)
.from(QProduct.product)
.where(Expressions.booleanTemplate(
"json_contains_key({0}, {1})",
QProduct.product.categories,
Expressions.constants("1234").isTrue())
.fetch();
但是,这并不像您在 Querydsl 中习惯的那样流畅。可以使用自定义表达式类型扩展 Querydsl,因此假设引入了 JsonExpression。还可以扩展querydsl-apt 以在静态元模型(Q 类)中自动生成此表达式类型的路径。我专门为此目的构建了扩展库hibernate-types-querydsl-apt。它为来自Hibernate-Types 项目的自定义类型(例如范围、数组、json、hstore、间隔)添加了各种自定义表达式类型。它还自动为主要操作注册自定义函数。使用我的扩展,数组的用例将很简单:
@Entity
@TypeDefs({
@TypeDef(name = "int-array", typeClass = IntArrayType.class, defaultForType = int[].class)
})
public class ArrayEntity {
@Id
Long id;
@Type(type = "int-array")
@Column(name = "sensor_values", columnDefinition = "integer[]")
int[] sensorValues;
}
List<Tuple> fetch = new JPAQuery<>(entityManager, ExtendedHQLTemplates.DEFAULT)
.from(arrayEntity)
.select(arrayEntity)
.where(arrayEntity.sensorValues.contains(123))
.fetch();
更多示例请访问:https://github.com/jwgmeligmeyling/hibernate-types-querydsl-apt/blob/master/querydsl-ext-testsuite/src/test/java/com/pallasathenagroup/querydsl/ArrayEntityPathTest.java。